Add teams tab
TNL-1939
This commit is contained in:
21
common/test/acceptance/pages/lms/teams.py
Normal file
21
common/test/acceptance/pages/lms/teams.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Teams page.
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
|
||||
|
||||
class TeamsPage(CoursePage):
|
||||
"""
|
||||
Teams page/tab.
|
||||
"""
|
||||
url_path = "teams"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
""" Checks if teams page is being viewed """
|
||||
return self.q(css='body.view-teams').present
|
||||
|
||||
def get_body_text(self):
|
||||
""" Returns the current dummy text. This will be changed once there is more content on the page. """
|
||||
return self.q(css='.teams-text').text[0]
|
||||
108
common/test/acceptance/tests/lms/test_teams.py
Normal file
108
common/test/acceptance/tests/lms/test_teams.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Acceptance tests for the teams feature.
|
||||
"""
|
||||
from ..helpers import UniqueCourseTest
|
||||
from ...pages.lms.teams import TeamsPage
|
||||
from nose.plugins.attrib import attr
|
||||
from ...fixtures.course import CourseFixture
|
||||
from ...pages.lms.tab_nav import TabNavPage
|
||||
from ...pages.lms.auto_auth import AutoAuthPage
|
||||
from ...pages.lms.course_info import CourseInfoPage
|
||||
|
||||
|
||||
@attr('shard_5')
|
||||
class TeamsTabTest(UniqueCourseTest):
|
||||
"""
|
||||
Tests verifying when the Teams tab is present.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TeamsTabTest, self).setUp()
|
||||
self.tab_nav = TabNavPage(self.browser)
|
||||
self.course_info_page = CourseInfoPage(self.browser, self.course_id)
|
||||
self.teams_page = TeamsPage(self.browser, self.course_id)
|
||||
self.test_topic = {u"name": u"a topic", u"description": u"test topic", u"id": 0}
|
||||
|
||||
def set_team_configuration(self, configuration, enroll_in_course=True, global_staff=False):
|
||||
"""
|
||||
Sets team configuration on the course and calls auto-auth on the user.
|
||||
"""
|
||||
#pylint: disable=attribute-defined-outside-init
|
||||
self.course_fixture = CourseFixture(**self.course_info)
|
||||
if configuration:
|
||||
self.course_fixture.add_advanced_settings(
|
||||
{u"teams_configuration": {u"value": configuration}}
|
||||
)
|
||||
self.course_fixture.install()
|
||||
|
||||
enroll_course_id = self.course_id if enroll_in_course else None
|
||||
AutoAuthPage(self.browser, course_id=enroll_course_id, staff=global_staff).visit()
|
||||
self.course_info_page.visit()
|
||||
|
||||
def verify_teams_present(self, present):
|
||||
"""
|
||||
Verifies whether or not the teams tab is present. If it should be present, also
|
||||
checks the text on the page (to ensure view is working).
|
||||
"""
|
||||
if present:
|
||||
self.assertIn("Teams", self.tab_nav.tab_names)
|
||||
self.teams_page.visit()
|
||||
self.assertEqual("This is the new Teams tab.", self.teams_page.get_body_text())
|
||||
else:
|
||||
self.assertNotIn("Teams", self.tab_nav.tab_names)
|
||||
|
||||
def test_teams_not_enabled(self):
|
||||
"""
|
||||
Scenario: teams tab should not be present if no team configuration is set
|
||||
Given I am enrolled in a course without team configuration
|
||||
When I view the course info page
|
||||
Then I should not see the Teams tab
|
||||
"""
|
||||
self.set_team_configuration(None)
|
||||
self.verify_teams_present(False)
|
||||
|
||||
def test_teams_not_enabled_no_topics(self):
|
||||
"""
|
||||
Scenario: teams tab should not be present if team configuration does not specify topics
|
||||
Given I am enrolled in a course with no topics in the team configuration
|
||||
When I view the course info page
|
||||
Then I should not see the Teams tab
|
||||
"""
|
||||
self.set_team_configuration({u"max_team_size": 10, u"topics": []})
|
||||
self.verify_teams_present(False)
|
||||
|
||||
def test_teams_not_enabled_not_enrolled(self):
|
||||
"""
|
||||
Scenario: teams tab should not be present if student is not enrolled in the course
|
||||
Given there is a course with team configuration and topics
|
||||
And I am not enrolled in that course, and am not global staff
|
||||
When I view the course info page
|
||||
Then I should not see the Teams tab
|
||||
"""
|
||||
self.set_team_configuration({u"max_team_size": 10, u"topics": [self.test_topic]}, enroll_in_course=False)
|
||||
self.verify_teams_present(False)
|
||||
|
||||
def test_teams_enabled(self):
|
||||
"""
|
||||
Scenario: teams tab should be present if user is enrolled in the course and it has team configuration
|
||||
Given I am enrolled in a course with team configuration and topics
|
||||
When I view the course info page
|
||||
Then I should see the Teams tab
|
||||
And the correct content should be on the page
|
||||
"""
|
||||
self.set_team_configuration({u"max_team_size": 10, u"topics": [self.test_topic]})
|
||||
self.verify_teams_present(True)
|
||||
|
||||
def test_teams_enabled_global_staff(self):
|
||||
"""
|
||||
Scenario: teams tab should be present if user is not enrolled in the course, but is global staff
|
||||
Given there is a course with team configuration
|
||||
And I am not enrolled in that course, but am global staff
|
||||
When I view the course info page
|
||||
Then I should see the Teams tab
|
||||
And the correct content should be on the page
|
||||
"""
|
||||
self.set_team_configuration(
|
||||
{u"max_team_size": 10, u"topics": [self.test_topic]}, enroll_in_course=False, global_staff=True
|
||||
)
|
||||
self.verify_teams_present(True)
|
||||
0
lms/djangoapps/teams/__init__.py
Normal file
0
lms/djangoapps/teams/__init__.py
Normal file
30
lms/djangoapps/teams/plugins.py
Normal file
30
lms/djangoapps/teams/plugins.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Definition of the course team feature.
|
||||
"""
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from courseware.tabs import EnrolledCourseViewType
|
||||
from .views import is_feature_enabled
|
||||
|
||||
|
||||
class TeamsCourseViewType(EnrolledCourseViewType):
|
||||
"""
|
||||
The representation of the course teams view type.
|
||||
"""
|
||||
|
||||
name = "teams"
|
||||
title = _("Teams")
|
||||
view_name = "teams_dashboard"
|
||||
|
||||
@classmethod
|
||||
def is_enabled(cls, course, user=None):
|
||||
"""Returns true if the teams feature is enabled in the course.
|
||||
|
||||
Args:
|
||||
course (CourseDescriptor): the course using the feature
|
||||
user (User): the user interacting with the course
|
||||
"""
|
||||
if not super(TeamsCourseViewType, cls).is_enabled(course, user=user):
|
||||
return False
|
||||
|
||||
return is_feature_enabled(course)
|
||||
@@ -0,0 +1,19 @@
|
||||
define(["jquery", "teams/js/teams_tab_factory"],
|
||||
function($, TeamsTabFactory) {
|
||||
'use strict';
|
||||
|
||||
describe("teams django app", function() {
|
||||
var teamsTab;
|
||||
|
||||
beforeEach(function() {
|
||||
setFixtures("<div class='team-tab-content'></div>");
|
||||
teamsTab = new TeamsTabFactory();
|
||||
});
|
||||
|
||||
it("can load templates", function() {
|
||||
expect($("body").text()).toContain("This is the new Teams tab");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
);
|
||||
13
lms/djangoapps/teams/static/teams/js/teams_tab_factory.js
Normal file
13
lms/djangoapps/teams/static/teams/js/teams_tab_factory.js
Normal file
@@ -0,0 +1,13 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define(['jquery', 'teams/js/views/teams_tab'],
|
||||
function ($, TeamsTabView) {
|
||||
return function () {
|
||||
var view = new TeamsTabView({
|
||||
el: $('.team-tab-content')
|
||||
});
|
||||
view.render();
|
||||
};
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
14
lms/djangoapps/teams/static/teams/js/views/teams_tab.js
Normal file
14
lms/djangoapps/teams/static/teams/js/views/teams_tab.js
Normal file
@@ -0,0 +1,14 @@
|
||||
;(function (define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone', 'underscore', 'text!teams/templates/teams-tab.underscore'],
|
||||
function (Backbone, _, teamsTabTemplate) {
|
||||
var TeamTabView = Backbone.View.extend({
|
||||
render: function() {
|
||||
this.$el.html(_.template(teamsTabTemplate, {}));
|
||||
}
|
||||
});
|
||||
|
||||
return TeamTabView;
|
||||
});
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1 @@
|
||||
<p class="teams-text">This is the new Teams tab.</p>
|
||||
25
lms/djangoapps/teams/templates/teams/teams.html
Normal file
25
lms/djangoapps/teams/templates/teams/teams.html
Normal file
@@ -0,0 +1,25 @@
|
||||
## mako
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%namespace name='static' file='/static_content.html'/>
|
||||
<%inherit file="/main.html" />
|
||||
|
||||
<%block name="bodyclass">view-teams is-in-course course</%block>
|
||||
<%block name="pagetitle">${_("Teams")}</%block>
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course'/>
|
||||
</%block>
|
||||
|
||||
<%include file="/courseware/course_navigation.html" args="active_page='teams'" />
|
||||
|
||||
<div class="team-tab-content"></div>
|
||||
|
||||
<%block name="js_extra">
|
||||
<script type="text/javascript">
|
||||
(function (require) {
|
||||
require(['teams/js/teams_tab_factory'], function (TeamsTabFactory) {
|
||||
var pageView = new TeamsTabFactory({
|
||||
});
|
||||
});
|
||||
}).call(this, require || RequireJS.require);
|
||||
</script>
|
||||
</%block>
|
||||
85
lms/djangoapps/teams/tests/test_views.py
Normal file
85
lms/djangoapps/teams/tests/test_views.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Tests for views.py
|
||||
"""
|
||||
from nose.plugins.attrib import attr
|
||||
from student.tests.factories import (
|
||||
CourseEnrollmentFactory,
|
||||
UserFactory,
|
||||
)
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from django.http import Http404
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class TestDashboard(ModuleStoreTestCase):
|
||||
test_password = "test"
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up tests
|
||||
"""
|
||||
super(TestDashboard, self).setUp()
|
||||
self.course = CourseFactory.create(
|
||||
teams_configuration={"max_team_size": 10, "topics": [{"name": "foo", "id": 0, "description": "test topic"}]}
|
||||
)
|
||||
# will be assigned to self.client by default
|
||||
self.user = UserFactory.create(password=self.test_password)
|
||||
self.teams_url = reverse('teams_dashboard', args=[self.course.id])
|
||||
|
||||
def test_anonymous(self):
|
||||
""" Verifies that an anonymous client cannot access the team dashboard. """
|
||||
anonymous_client = APIClient()
|
||||
response = anonymous_client.get(self.teams_url)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
def test_not_enrolled_not_staff(self):
|
||||
""" Verifies that a student who is not enrolled cannot access the team dashboard. """
|
||||
response = self.client.get(self.teams_url)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
def test_not_enrolled_staff(self):
|
||||
"""
|
||||
Verifies that a user with global access who is not enrolled in the course can access the team dashboard.
|
||||
"""
|
||||
staff_user = UserFactory(is_staff=True, password=self.test_password)
|
||||
staff_client = APIClient()
|
||||
staff_client.login(username=staff_user.username, password=self.test_password)
|
||||
response = staff_client.get(self.teams_url)
|
||||
self.assertContains(response, "TeamsTabFactory", status_code=200)
|
||||
|
||||
def test_enrolled_not_staff(self):
|
||||
"""
|
||||
Verifies that a user without global access who is enrolled in the course can access the team dashboard.
|
||||
"""
|
||||
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id)
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
response = self.client.get(self.teams_url)
|
||||
self.assertContains(response, "TeamsTabFactory", status_code=200)
|
||||
|
||||
def test_enrolled_teams_not_enabled(self):
|
||||
"""
|
||||
Verifies that a user without global access who is enrolled in the course cannot access the team dashboard
|
||||
if the teams feature is not enabled.
|
||||
"""
|
||||
course = CourseFactory.create()
|
||||
teams_url = reverse('teams_dashboard', args=[course.id])
|
||||
CourseEnrollmentFactory.create(user=self.user, course_id=course.id)
|
||||
self.client.login(username=self.user.username, password=self.test_password)
|
||||
response = self.client.get(teams_url)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
def test_bad_course_id(self):
|
||||
"""
|
||||
Verifies expected behavior when course_id does not reference an existing course or is invalid.
|
||||
"""
|
||||
bad_org = "badorgxxx"
|
||||
bad_team_url = self.teams_url.replace(self.course.id.org, bad_org)
|
||||
response = self.client.get(bad_team_url)
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
bad_team_url = bad_team_url.replace(bad_org, "invalid/course/id")
|
||||
response = self.client.get(bad_team_url)
|
||||
self.assertEqual(404, response.status_code)
|
||||
11
lms/djangoapps/teams/urls.py
Normal file
11
lms/djangoapps/teams/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
URLs for teams.
|
||||
"""
|
||||
from django.conf.urls import patterns, url
|
||||
from teams.views import TeamsDashboardView
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
"teams.views",
|
||||
url(r"^/$", TeamsDashboardView.as_view(), name="teams_dashboard"),
|
||||
)
|
||||
44
lms/djangoapps/teams/views.py
Normal file
44
lms/djangoapps/teams/views.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
View methods for the course team feature.
|
||||
"""
|
||||
|
||||
from django.shortcuts import render_to_response
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from courseware.courses import get_course_with_access, has_access
|
||||
from django.http import Http404
|
||||
from django.conf import settings
|
||||
from django.views.generic.base import View
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
|
||||
class TeamsDashboardView(View):
|
||||
"""
|
||||
View methods related to the teams dashboard.
|
||||
"""
|
||||
|
||||
def get(self, request, course_id):
|
||||
"""
|
||||
Renders the teams dashboard, which is shown on the "Teams" tab.
|
||||
|
||||
Raises a 404 if the course specified by course_id does not exist, the
|
||||
user is not registered for the course, or the teams feature is not enabled.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, "load", course_key)
|
||||
|
||||
if not is_feature_enabled(course):
|
||||
raise Http404
|
||||
|
||||
if not CourseEnrollment.is_enrolled(request.user, course.id) and \
|
||||
not has_access(request.user, 'staff', course, course.id):
|
||||
raise Http404
|
||||
|
||||
context = {"course": course}
|
||||
return render_to_response("teams/teams.html", context)
|
||||
|
||||
|
||||
def is_feature_enabled(course):
|
||||
"""
|
||||
Returns True if the teams feature is enabled.
|
||||
"""
|
||||
return settings.FEATURES.get('ENABLE_TEAMS', False) and course.teams_enabled
|
||||
@@ -1854,6 +1854,9 @@ INSTALLED_APPS = (
|
||||
|
||||
# Credit courses
|
||||
'openedx.core.djangoapps.credit',
|
||||
|
||||
# Course teams
|
||||
'teams',
|
||||
)
|
||||
|
||||
######################### CSRF #########################################
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
'jquery.url': 'xmodule_js/common_static/js/vendor/url.min',
|
||||
'datepair': 'xmodule_js/common_static/js/vendor/timepicker/datepair',
|
||||
'date': 'xmodule_js/common_static/js/vendor/date',
|
||||
'text': 'xmodule_js/common_static/js/vendor/requirejs/text',
|
||||
'underscore': 'xmodule_js/common_static/js/vendor/underscore-min',
|
||||
'underscore.string': 'xmodule_js/common_static/js/vendor/underscore.string.min',
|
||||
'backbone': 'xmodule_js/common_static/js/vendor/backbone-min',
|
||||
@@ -576,6 +577,7 @@
|
||||
// TODO: why do these need 'lms/include' at the front but the CMS equivalent logic doesn't?
|
||||
define([
|
||||
// Run the LMS tests
|
||||
'lms/include/teams/js/spec/teams_factory_spec.js',
|
||||
'lms/include/js/spec/photocapture_spec.js',
|
||||
'lms/include/js/spec/staff_debug_actions_spec.js',
|
||||
'lms/include/js/spec/views/notification_spec.js',
|
||||
|
||||
@@ -35,6 +35,7 @@ lib_paths:
|
||||
- xmodule_js/common_static/js/vendor/jasmine-imagediff.js
|
||||
- xmodule_js/common_static/js/vendor/require.js
|
||||
- js/RequireJS-namespace-undefine.js
|
||||
- xmodule_js/common_static/js/vendor/requirejs/text.js
|
||||
- xmodule_js/common_static/js/vendor/jquery.min.js
|
||||
- xmodule_js/common_static/js/vendor/jquery-ui.min.js
|
||||
- xmodule_js/common_static/js/vendor/jquery.cookie.js
|
||||
@@ -64,10 +65,12 @@ lib_paths:
|
||||
src_paths:
|
||||
- js
|
||||
- js/common_helpers
|
||||
- teams/js
|
||||
|
||||
# Paths to spec (test) JavaScript files
|
||||
spec_paths:
|
||||
- js/spec
|
||||
- teams/js/spec
|
||||
|
||||
# Paths to fixture files (optional)
|
||||
# The fixture path will be set automatically when using jasmine-jquery.
|
||||
@@ -91,6 +94,7 @@ fixture_paths:
|
||||
- js/fixtures/edxnotes
|
||||
- js/fixtures/search
|
||||
- templates/search
|
||||
- teams/templates
|
||||
- templates/discovery
|
||||
|
||||
requirejs:
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
paths: {
|
||||
"annotator_1.2.9": "js/vendor/edxnotes/annotator-full.min",
|
||||
"date": "js/vendor/date",
|
||||
"text": 'js/vendor/requirejs/text',
|
||||
"backbone": "js/vendor/backbone-min",
|
||||
"backbone-super": "js/vendor/backbone-super",
|
||||
"underscore.string": "js/vendor/underscore.string.min",
|
||||
@@ -67,7 +68,7 @@
|
||||
"osda": 'js/vendor/ova/OpenSeaDragonAnnotation',
|
||||
"ova": 'js/vendor/ova/ova',
|
||||
"catch": 'js/vendor/ova/catch/js/catch',
|
||||
"handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2',
|
||||
"handlebars": 'js/vendor/ova/catch/js/handlebars-1.1.2'
|
||||
// end of files needed by OVA
|
||||
},
|
||||
shim: {
|
||||
@@ -89,7 +90,7 @@
|
||||
exports: "Backbone"
|
||||
},
|
||||
"backbone-super": {
|
||||
deps: ["backbone"],
|
||||
deps: ["backbone"]
|
||||
},
|
||||
"logger": {
|
||||
exports: "Logger"
|
||||
@@ -147,7 +148,7 @@
|
||||
"grouping-annotator", "diacritic-annotator", "openseadragon", "jquery-Watch", "catch", "handlebars",
|
||||
"URI"
|
||||
]
|
||||
},
|
||||
}
|
||||
// End of needed by OVA
|
||||
}
|
||||
};
|
||||
|
||||
1
lms/static/teams
Symbolic link
1
lms/static/teams
Symbolic link
@@ -0,0 +1 @@
|
||||
../djangoapps/teams/static/teams
|
||||
@@ -428,6 +428,12 @@ if settings.COURSEWARE_ENABLED:
|
||||
url(r'^api/branding/v1/', include('branding.api_urls')),
|
||||
)
|
||||
|
||||
if settings.FEATURES["ENABLE_TEAMS"]:
|
||||
# Teams endpoints
|
||||
urlpatterns += (
|
||||
url(r'^courses/{}/teams'.format(settings.COURSE_ID_PATTERN), include('teams.urls'), name="teams_endpoints"),
|
||||
)
|
||||
|
||||
# allow course staff to change to student view of courseware
|
||||
if settings.FEATURES.get('ENABLE_MASQUERADE'):
|
||||
urlpatterns += (
|
||||
|
||||
1
setup.py
1
setup.py
@@ -33,6 +33,7 @@ setup(
|
||||
"progress = lms.djangoapps.courseware.tabs:ProgressCourseViewType",
|
||||
"static_tab = lms.djangoapps.courseware.tabs:StaticCourseViewType",
|
||||
"syllabus = lms.djangoapps.courseware.tabs:SyllabusCourseViewType",
|
||||
"teams = lms.djangoapps.teams.plugins:TeamsCourseViewType",
|
||||
"textbooks = lms.djangoapps.courseware.tabs:TextbookCourseViews",
|
||||
"wiki = lms.djangoapps.course_wiki.tab:WikiCourseViewType",
|
||||
|
||||
|
||||
Reference in New Issue
Block a user