Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grant admin rights to members of the course team #470

Merged
merged 36 commits into from
Jul 30, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c240f65
Reformat JS
singingwolfboy Jul 22, 2013
21a3237
Reorganize URLs and views around course team
singingwolfboy Jul 22, 2013
5c356bd
Add button to add/remove instructor priviledges on course team page
singingwolfboy Jul 22, 2013
6a9074e
Removed `get_url_reverse` function
singingwolfboy Jul 23, 2013
b835f7c
Update a manage_user reverse call
singingwolfboy Jul 23, 2013
724ef2e
Fixing test failures
singingwolfboy Jul 23, 2013
97a02d4
Make assertion failure message more understandable
singingwolfboy Jul 23, 2013
f438552
Added unit tests for new course team API
singingwolfboy Jul 23, 2013
0682157
Test manage_users view for user that is a member of the course team
singingwolfboy Jul 23, 2013
c70bd5c
Remove whitespace from email addresses on the course team page
singingwolfboy Jul 23, 2013
b6c6954
Check for instructor role before removing it
singingwolfboy Jul 23, 2013
79c554b
course admin team: handle is_staff users
singingwolfboy Jul 24, 2013
4233146
Can't remove last instructor of a course
singingwolfboy Jul 24, 2013
a1b44af
Only instructors may make other instructors on a course
singingwolfboy Jul 24, 2013
8a10695
Extend runone script to accept pdb flags
singingwolfboy Jul 24, 2013
4a2b551
Studio: styles new user role controls and revamps course team UI
talbs Jul 25, 2013
91f192f
Added error prompts for the course team page
singingwolfboy Jul 25, 2013
5738e4f
Pull correct URL when changing user permissions for course team
singingwolfboy Jul 25, 2013
ecf855e
Fixup translations
singingwolfboy Jul 25, 2013
4183274
Correct course team admin badging logic
singingwolfboy Jul 25, 2013
e5ef5ef
Show disabled trash icon instead of not showing it at all
singingwolfboy Jul 25, 2013
deced24
Studio: refactored form-based Sass and revised markup/copy for cours…
talbs Jul 25, 2013
36a1087
Fix variable scoping issue in Mako template
singingwolfboy Jul 29, 2013
0bd25c0
Fix up lettuce tests for course team page redesign
singingwolfboy Jul 29, 2013
e4bd862
Updated changelog for course team admin story
singingwolfboy Jul 29, 2013
64566c1
Fix unit tests
singingwolfboy Jul 29, 2013
6980e9f
Studio: bulletproofs actions on course team view
talbs Jul 29, 2013
611dab0
Studio: places 'add team member' call to course team view
talbs Jul 29, 2013
8b6c278
copy changes
markchang Jul 30, 2013
2161961
Studio: corrects legend element copy on new user form
talbs Jul 30, 2013
c82ad6c
Studio: revises badge and create call to action design
talbs Jul 30, 2013
fba5058
Add a lettuce test for course team admins granting admin status
singingwolfboy Jul 30, 2013
7a41855
Studio: adds logic to show add new user to team view
talbs Jul 30, 2013
573445a
Studio: adds badge UI firefox fix
talbs Jul 30, 2013
eaac4f8
Add a lettuce test for course team admins removing admin status
singingwolfboy Jul 30, 2013
0022469
Change "user" to "admin" in course team BDD descriptions
singingwolfboy Jul 30, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ the setting is not present, the API is disabled).
LMS: Added endpoints for AJAX requests to enable/disable notifications
(which are not yet implemented) and a one-click unsubscribe page.

Studio: Allow instructors of a course to designate other staff as instructors;
this allows instructors to hand off management of a course to someone else.

Common: Add a manage.py that knows about edx-platform specific settings and projects

Common: Added *experimental* support for jsinput type.
Expand Down
6 changes: 4 additions & 2 deletions cms/djangoapps/auth/authz.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,12 @@ def _remove_user_from_group(user, group_name):
user.save()


def is_user_in_course_group_role(user, location, role):
def is_user_in_course_group_role(user, location, role, check_staff=True):
if user.is_active and user.is_authenticated:
# all "is_staff" flagged accounts belong to all groups
return user.is_staff or user.groups.filter(name=get_course_groupname_for_role(location, role)).count() > 0
if check_staff and user.is_staff:
return True
return user.groups.filter(name=get_course_groupname_for_role(location, role)).count() > 0

return False

Expand Down
10 changes: 10 additions & 0 deletions cms/djangoapps/contentstore/features/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ def i_have_opened_a_new_course(_step):
open_new_course()


@step('(I select|s?he selects) the new course')
def select_new_course(_step, whom):
course_link_xpath = '//div[contains(@class, "courses")]//a[contains(@class, "class-link")]//span[contains(., "{name}")]/..'.format(
name="Robot Super Course")
element = world.browser.find_by_xpath(course_link_xpath)
element.click()


@step(u'I press the "([^"]*)" notification button$')
def press_the_notification_button(_step, name):
css = 'a.action-%s' % name.lower()
Expand Down Expand Up @@ -118,6 +126,8 @@ def create_studio_user(
registration.register(studio_user)
registration.activate()

return studio_user


def fill_in_course_info(
name='Robot Super Course',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ def create_component_instance(step, component_button_css, category,
if has_multiple_templates:
click_component_from_menu(category, boilerplate, expected_css)

assert_equal(1, len(world.css_find(expected_css)))
assert_equal(
1,
len(world.css_find(expected_css)),
"Component instance with css {css} was not created successfully".format(css=expected_css))


@world.absorb
def click_new_component_button(step, component_button_css):
Expand Down
33 changes: 29 additions & 4 deletions cms/djangoapps/contentstore/features/course-team.feature
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
Feature: Course Team
As a course author, I want to be able to add others to my team

Scenario: Users can add other users
Scenario: Admins can add other users
Given I have opened a new course in Studio
And the user "alice" exists
And I am viewing the course team settings
When I add "alice" to the course team
And "alice" logs in
Then she does see the course on her page

Scenario: Added users cannot delete or add other users
Scenario: Added admins cannot delete or add other users
Given I have opened a new course in Studio
And the user "bob" exists
And I am viewing the course team settings
Expand All @@ -18,7 +18,7 @@ Feature: Course Team
Then he cannot delete users
And he cannot add users

Scenario: Users can delete other users
Scenario: Admins can delete other users
Given I have opened a new course in Studio
And the user "carol" exists
And I am viewing the course team settings
Expand All @@ -27,8 +27,33 @@ Feature: Course Team
And "carol" logs in
Then she does not see the course on her page

Scenario: Users cannot add users that do not exist
Scenario: Admins cannot add users that do not exist
Given I have opened a new course in Studio
And I am viewing the course team settings
When I add "dennis" to the course team
Then I should see "Could not find user by email address" somewhere on the page

Scenario: Admins should be able to make other people into admins
Given I have opened a new course in Studio
And the user "emily" exists
And I am viewing the course team settings
And I add "emily" to the course team
When I make "emily" a course team admin
And "emily" logs in
And she selects the new course
And she views the course team settings
Then "emily" should be marked as an admin
And she can add users
And she can delete users

Scenario: Admins should be able to remove other admins
Given I have opened a new course in Studio
And the user "frank" exists as a course admin
And I am viewing the course team settings
When I remove admin rights from "frank"
And "frank" logs in
And he selects the new course
And he views the course team settings
Then "frank" should not be marked as an admin
And he cannot add users
And he cannot delete users
76 changes: 58 additions & 18 deletions cms/djangoapps/contentstore/features/course-team.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,105 @@

from lettuce import world, step
from common import create_studio_user, log_into_studio
from django.contrib.auth.models import Group
from auth.authz import get_course_groupname_for_role

PASSWORD = 'test'
EMAIL_EXTENSION = '@edx.org'


@step(u'I am viewing the course team settings')
def view_grading_settings(_step):
@step(u'(I am viewing|s?he views) the course team settings')
def view_grading_settings(_step, whom):
world.click_course_settings()
link_css = 'li.nav-course-settings-team a'
world.css_click(link_css)


@step(u'the user "([^"]*)" exists$')
def create_other_user(_step, name):
create_studio_user(uname=name, password=PASSWORD, email=(name + EMAIL_EXTENSION))
@step(u'the user "([^"]*)" exists( as a course admin)?$')
def create_other_user(_step, name, course_admin):
user = create_studio_user(uname=name, password=PASSWORD, email=(name + EMAIL_EXTENSION))
if course_admin:
location = world.scenario_dict["COURSE"].location
for role in ("staff", "instructor"):
group, __ = Group.objects.get_or_create(name=get_course_groupname_for_role(location, role))
user.groups.add(group)
user.save()


@step(u'I add "([^"]*)" to the course team')
def add_other_user(_step, name):
new_user_css = 'a.new-user-button'
new_user_css = 'a.create-user-button'
world.css_click(new_user_css)
world.wait(0.5)

email_css = 'input.email-input'
email_css = 'input#user-email-input'
f = world.css_find(email_css)
f._element.send_keys(name, EMAIL_EXTENSION)

confirm_css = '#add_user'
confirm_css = 'form.create-user button.action-primary'
world.css_click(confirm_css)


@step(u'I delete "([^"]*)" from the course team')
def delete_other_user(_step, name):
to_delete_css = 'a.remove-user[data-id="{name}{extension}"]'.format(name=name, extension=EMAIL_EXTENSION)
to_delete_css = '.user-item .item-actions a.remove-user[data-id="{email}"]'.format(
email="{0}{1}".format(name, EMAIL_EXTENSION))
world.css_click(to_delete_css)


@step(u'I make "([^"]*)" a course team admin')
def make_course_team_admin(_step, name):
admin_btn_css = '.user-item[data-email="{email}"] .user-actions .add-admin-role'.format(
email=name+EMAIL_EXTENSION)
world.css_click(admin_btn_css)


@step(u'I remove admin rights from "([^"]*)"')
def remove_course_team_admin(_step, name):
admin_btn_css = '.user-item[data-email="{email}"] .user-actions .remove-admin-role'.format(
email=name+EMAIL_EXTENSION)
world.css_click(admin_btn_css)


@step(u'"([^"]*)" logs in$')
def other_user_login(_step, name):
log_into_studio(uname=name, password=PASSWORD, email=name + EMAIL_EXTENSION)


@step(u's?he does( not)? see the course on (his|her) page')
def see_course(_step, doesnt_see_course, gender):
def see_course(_step, inverted, gender):
class_css = 'span.class-name'
all_courses = world.css_find(class_css, wait_time=1)
all_names = [item.html for item in all_courses]
if doesnt_see_course:
if inverted:
assert not world.scenario_dict['COURSE'].display_name in all_names
else:
assert world.scenario_dict['COURSE'].display_name in all_names


@step(u's?he cannot delete users')
def cannot_delete(_step):
@step(u'"([^"]*)" should( not)? be marked as an admin')
def marked_as_admin(_step, name, inverted):
flag_css = '.user-item[data-email="{email}"] .flag-role.flag-role-admin'.format(
email=name+EMAIL_EXTENSION)
if inverted:
assert world.is_css_not_present(flag_css)
else:
assert world.is_css_present(flag_css)


@step(u's?he can(not)? delete users')
def can_delete_users(_step, inverted):
to_delete_css = 'a.remove-user'
assert world.is_css_not_present(to_delete_css)
if inverted:
assert world.is_css_not_present(to_delete_css)
else:
assert world.is_css_present(to_delete_css)


@step(u's?he cannot add users')
def cannot_add(_step):
add_css = 'a.new-user'
assert world.is_css_not_present(add_css)
@step(u's?he can(not)? add users')
def can_add_users(_step, inverted):
add_css = 'a.create-user-button'
if inverted:
assert world.is_css_not_present(add_css)
else:
assert world.is_css_present(add_css)
8 changes: 6 additions & 2 deletions cms/djangoapps/contentstore/tests/test_checklists.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
""" Unit tests for checklist methods in views.py. """
from contentstore.utils import get_modulestore, get_url_reverse
from contentstore.utils import get_modulestore
from xmodule.modulestore.inheritance import own_metadata
from xmodule.modulestore.tests.factories import CourseFactory
from django.core.urlresolvers import reverse
Expand Down Expand Up @@ -38,7 +38,11 @@ def compare_checklists(self, persisted, request):

def test_get_checklists(self):
""" Tests the get checklists method. """
checklists_url = get_url_reverse('Checklists', self.course)
checklists_url = reverse("checklists", kwargs={
'org': self.course.location.org,
'course': self.course.location.course,
'name': self.course.location.name,
})
response = self.client.get(checklists_url)
self.assertContains(response, "Getting Started With Studio")
payload = response.content
Expand Down
4 changes: 3 additions & 1 deletion cms/djangoapps/contentstore/tests/test_contentstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,9 @@ def test_cms_imported_course_walkthrough(self):

# manage users
resp = self.client.get(reverse('manage_users',
kwargs={'location': loc.url()}))
kwargs={'org': loc.org,
'course': loc.course,
'name': loc.name}))
self.assertEqual(200, resp.status_code)

# course info
Expand Down
Loading