Commit 21a32370 by David Baumgold

Reorganize URLs and views around course team

Match other views better, saner URLs, more RESTful style, extensible for other roles
parent c240f659
import json
from django.conf import settings from django.conf import settings
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from django_future.csrf import ensure_csrf_cookie from django_future.csrf import ensure_csrf_cookie
...@@ -10,9 +12,9 @@ from django.core.context_processors import csrf ...@@ -10,9 +12,9 @@ from django.core.context_processors import csrf
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from contentstore.utils import get_url_reverse, get_lms_link_for_item from contentstore.utils import get_url_reverse, get_lms_link_for_item
from util.json_request import expect_json, JsonResponse from util.json_request import JsonResponse
from auth.authz import STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_users_in_course_group_by_role from auth.authz import STAFF_ROLE_NAME, INSTRUCTOR_ROLE_NAME, get_users_in_course_group_by_role
from auth.authz import get_user_by_email, add_user_to_course_group, remove_user_from_course_group from auth.authz import add_user_to_course_group, remove_user_from_course_group
from course_creators.views import get_course_creator_status, add_user_with_status_unrequested, user_requested_access from course_creators.views import get_course_creator_status, add_user_with_status_unrequested, user_requested_access
from .access import has_access from .access import has_access
...@@ -60,10 +62,11 @@ def request_course_creator(request): ...@@ -60,10 +62,11 @@ def request_course_creator(request):
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def manage_users(request, location): def manage_users(request, org, course, name):
''' '''
This view will return all CMS users who are editors for the specified course This view will return all CMS users who are editors for the specified course
''' '''
location = Location('i4x', org, course, 'course', name)
# check that logged in user has permissions to this item # check that logged in user has permissions to this item
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME): if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME):
raise PermissionDenied() raise PermissionDenied()
...@@ -73,91 +76,72 @@ def manage_users(request, location): ...@@ -73,91 +76,72 @@ def manage_users(request, location):
return render_to_response('manage_users.html', { return render_to_response('manage_users.html', {
'context_course': course_module, 'context_course': course_module,
'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME), 'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME),
'add_user_postback_url': reverse('add_user', args=[location]).rstrip('/'),
'remove_user_postback_url': reverse('remove_user', args=[location]).rstrip('/'),
'allow_actions': has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME), 'allow_actions': has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME),
'request_user_id': request.user.id
}) })
@expect_json
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def add_user(request, location): @require_http_methods(("GET", "POST", "PUT", "DELETE"))
''' def course_team_user(request, org, course, name, email):
This POST-back view will add a user - specified by email - to the list of editors for location = Location('i4x', org, course, 'course', name)
the specified course # check that logged in user has permissions to this item
''' if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=STAFF_ROLE_NAME):
email = request.POST.get("email")
if not email:
msg = {
'Status': 'Failed',
'ErrMsg': _('Please specify an email address.'),
}
return JsonResponse(msg, 400)
# remove leading/trailing whitespace if necessary
email = email.strip()
# check that logged in user has admin permissions to this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied() raise PermissionDenied()
user = get_user_by_email(email) try:
user = User.objects.get(email=email)
# user doesn't exist?!? Return error. except:
if user is None:
msg = { msg = {
'Status': 'Failed', "error": _("Could not find user by email address '{email}'.").format(email=email),
'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email),
} }
return JsonResponse(msg, 404) return JsonResponse(msg, 404)
# user exists, but hasn't activated account?!? if request.method == "GET":
if not user.is_active: # just return info about the user
roles = set()
for group in user.groups.all():
if not "_" in group.name:
continue
role, coursename = group.name.split("_", 1)
if coursename in (location.course, location.course_id):
roles.add(role)
msg = { msg = {
'Status': 'Failed', "email": user.email,
'ErrMsg': _('User {email} has registered but has not yet activated his/her account.').format(email=email), "active": user.is_active,
"roles": list(roles),
} }
return JsonResponse(msg, 400) return JsonResponse(msg)
# ok, we're cool to add to the course group
add_user_to_course_group(request.user, user, location, STAFF_ROLE_NAME)
return JsonResponse({"Status": "OK"})
@expect_json # can't modify an inactive user
@login_required if not user.is_active:
@ensure_csrf_cookie
def remove_user(request, location):
'''
This POST-back view will remove a user - specified by email - from the list of editors for
the specified course
'''
email = request.POST["email"]
# check that logged in user has admin permissions on this course
if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME):
raise PermissionDenied()
user = get_user_by_email(email)
if user is None:
msg = { msg = {
'Status': 'Failed', "error": _('User {email} has registered but has not yet activated his/her account.').format(email=email),
'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email),
} }
return JsonResponse(msg, 404) return JsonResponse(msg, 400)
# make sure we're not removing ourselves
if user.id == request.user.id:
raise PermissionDenied()
remove_user_from_course_group(request.user, user, location, STAFF_ROLE_NAME)
return JsonResponse({"Status": "OK"}) # all other operations require the requesting user to specify a role --
# or if no role is specified, default to "staff"
if "role" in request.POST:
role = request.POST["role"]
elif request.body:
try:
payload = json.loads(request.body)
except:
return JsonResponse({"error": _("malformed JSON")}, 400)
try:
role = payload["role"]
except KeyError:
return JsonResponse({"error": "`role` is required"}, 400)
else:
role = STAFF_ROLE_NAME
if request.method in ("POST", "PUT"):
add_user_to_course_group(request.user, user, location, role)
return JsonResponse()
elif request.method == "DELETE":
remove_user_from_course_group(request.user, user, location, role)
return JsonResponse()
def _get_course_creator_status(user): def _get_course_creator_status(user):
......
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%inherit file="base.html" /> <%inherit file="base.html" />
<%block name="title">${_("Course Team Settings")}</%block> <%block name="title">${_("Course Team Settings")}</%block>
<%block name="bodyclass">is-signedin course users settings team</%block> <%block name="bodyclass">is-signedin course users settings team</%block>
...@@ -46,12 +47,17 @@ ...@@ -46,12 +47,17 @@
<div> <div>
<ol class="user-list"> <ol class="user-list">
% for user in staff: % for user in staff:
<li> <li data-url="${reverse('course_team_user', kwargs=dict(
org=context_course.location.org,
course=context_course.location.course,
name=context_course.location.name,
email=user.email,
))}">
<span class="user-name">${user.username}</span> <span class="user-name">${user.username}</span>
<span class="user-email">${user.email}</span> <span class="user-email">${user.email}</span>
%if allow_actions : %if allow_actions :
<div class="item-actions"> <div class="item-actions">
%if request_user_id != user.id: %if request.user.id != user.id:
<a href="#" class="delete-button remove-user" data-id="${user.email}"><span class="delete-icon"></span></a> <a href="#" class="delete-button remove-user" data-id="${user.email}"><span class="delete-icon"></span></a>
%endif %endif
</div> </div>
...@@ -67,20 +73,25 @@ ...@@ -67,20 +73,25 @@
<%block name="jsextra"> <%block name="jsextra">
<script type="text/javascript"> <script type="text/javascript">
var addUserPostbackUrl = "${add_user_postback_url}"; var tplUserURL = "${reverse('course_team_user', kwargs=dict(
var removeUserPostbackUrl = "${remove_user_postback_url}"; org=context_course.location.org,
course=context_course.location.course,
name=context_course.location.name,
email="@@EMAIL@@",
))}"
$(document).ready(function() { $(document).ready(function() {
var $newUserForm = $('.new-user-form'); var $newUserForm = $('.new-user-form');
$newUserForm.bind('submit', function(e) { $newUserForm.bind('submit', function(e) {
e.preventDefault(); e.preventDefault();
var url = tplUserURL.replace("@@EMAIL@@", $('#email').val())
$.ajax({ $.ajax({
url: addUserPostbackUrl, url: url,
type: 'POST', type: 'POST',
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
data: { data: {
email: $('#email').val() role: 'staff',
}, },
success: function(data) { success: function(data) {
location.reload(); location.reload();
...@@ -88,7 +99,7 @@ ...@@ -88,7 +99,7 @@
notifyOnError: false, notifyOnError: false,
error: function(jqXHR, textStatus, errorThrown) { error: function(jqXHR, textStatus, errorThrown) {
data = JSON.parse(jqXHR.responseText); data = JSON.parse(jqXHR.responseText);
$('#result').show().empty().append(data.ErrMsg); $('#result').show().empty().append(data.error);
} }
}); });
}); });
...@@ -114,13 +125,14 @@ ...@@ -114,13 +125,14 @@
}); });
$('.remove-user').click(function() { $('.remove-user').click(function() {
var url = tplUserURL.replace("@@EMAIL@@", $(this).data('id'))
$.ajax({ $.ajax({
url: removeUserPostbackUrl, url: url,
type: 'POST', type: 'DELETE',
dataType: 'json', dataType: 'json',
contentType: 'application/json', contentType: 'application/json',
data: { data: {
email: $(this).data('id') role: 'staff',
}, },
complete: function() { complete: function() {
location.reload(); location.reload();
......
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
<a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Grading")}</a> <a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Grading")}</a>
</li> </li>
<li class="nav-item nav-course-settings-team"> <li class="nav-item nav-course-settings-team">
<a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">${_("Course Team")}</a> <a href="${reverse('manage_users', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Course Team")}</a>
</li> </li>
<li class="nav-item nav-course-settings-advanced"> <li class="nav-item nav-course-settings-advanced">
<a href="${reverse('course_advanced_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Advanced Settings")}</a> <a href="${reverse('course_advanced_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Advanced Settings")}</a>
......
...@@ -40,14 +40,12 @@ urlpatterns = ('', # nopep8 ...@@ -40,14 +40,12 @@ urlpatterns = ('', # nopep8
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)/upload_asset$',
'contentstore.views.upload_asset', name='upload_asset'), 'contentstore.views.upload_asset', name='upload_asset'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/team/(?P<name>[^/]+)$',
'contentstore.views.manage_users', name='manage_users'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/team/(?P<name>[^/]+)/(?P<email>[^/]+)$',
'contentstore.views.course_team_user', name='course_team_user'),
url(r'^manage_users/(?P<location>.*?)$', 'contentstore.views.manage_users', name='manage_users'),
url(r'^add_user/(?P<location>.*?)$',
'contentstore.views.add_user', name='add_user'),
url(r'^remove_user/(?P<location>.*?)$',
'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<name>[^/]+)/remove_user$',
'contentstore.views.remove_user', name='remove_user'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/info/(?P<name>[^/]+)$',
'contentstore.views.course_info', name='course_info'), 'contentstore.views.course_info', name='course_info'),
url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$', url(r'^(?P<org>[^/]+)/(?P<course>[^/]+)/course_info/updates/(?P<provided_id>.*)$',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment