Commit c15cf074 by Mark L. Chang

Merge branch 'master' of github.com:MITx/mitx into mchang/acceptance-testing

parents e776a0dc 1159b14b
h2 { h2 {
margin-top: 0; margin-top: 0;
margin-bottom: 15px; margin-bottom: 15px;
width: flex-grid(2, 9);
padding-right: flex-gutter(9);
border-right: 1px dashed #ddd;
@include box-sizing(border-box);
display: table-cell;
vertical-align: top;
&.problem-header { &.problem-header {
section.staff { section.staff {
...@@ -15,12 +9,6 @@ h2 { ...@@ -15,12 +9,6 @@ h2 {
} }
} }
@media screen and (max-width:1120px) {
display: block;
width: auto;
border-right: 0;
}
@media print { @media print {
display: block; display: block;
width: auto; width: auto;
...@@ -29,16 +17,6 @@ h2 { ...@@ -29,16 +17,6 @@ h2 {
} }
section.problem { section.problem {
display: table-cell;
width: flex-grid(7, 9);
padding-left: flex-gutter(9);
@media screen and (max-width:1120px) {
display: block;
width: auto;
padding: 0;
}
@media print { @media print {
display: block; display: block;
width: auto; width: auto;
......
...@@ -36,7 +36,7 @@ class WikiRedirectTestCase(PageLoader): ...@@ -36,7 +36,7 @@ class WikiRedirectTestCase(PageLoader):
""" """
Test that requesting wiki URLs redirect properly to or out of classes. Test that requesting wiki URLs redirect properly to or out of classes.
An enrolled in student going from /courses/edX/toy/2012_Fall/profile An enrolled in student going from /courses/edX/toy/2012_Fall/progress
to /wiki/some/fake/wiki/page/ will redirect to to /wiki/some/fake/wiki/page/ will redirect to
/courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/ /courses/edX/toy/2012_Fall/wiki/some/fake/wiki/page/
...@@ -48,10 +48,10 @@ class WikiRedirectTestCase(PageLoader): ...@@ -48,10 +48,10 @@ class WikiRedirectTestCase(PageLoader):
self.enroll(self.toy) self.enroll(self.toy)
referer = reverse("profile", kwargs={ 'course_id' : self.toy.id }) referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'}) destination = reverse("wiki:get", kwargs={'path': 'some/fake/wiki/page/'})
redirected_to = referer.replace("profile", "wiki/some/fake/wiki/page/") redirected_to = referer.replace("progress", "wiki/some/fake/wiki/page/")
resp = self.client.get( destination, HTTP_REFERER=referer) resp = self.client.get( destination, HTTP_REFERER=referer)
self.assertEqual(resp.status_code, 302 ) self.assertEqual(resp.status_code, 302 )
...@@ -77,11 +77,11 @@ class WikiRedirectTestCase(PageLoader): ...@@ -77,11 +77,11 @@ class WikiRedirectTestCase(PageLoader):
""" """
course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id}) course_wiki_home = reverse('course_wiki', kwargs={'course_id' : course.id})
referer = reverse("profile", kwargs={ 'course_id' : self.toy.id }) referer = reverse("progress", kwargs={ 'course_id' : self.toy.id })
resp = self.client.get(course_wiki_home, follow=True, HTTP_REFERER=referer) resp = self.client.get(course_wiki_home, follow=True, HTTP_REFERER=referer)
course_wiki_page = referer.replace('profile', 'wiki/' + self.toy.wiki_slug + "/") course_wiki_page = referer.replace('progress', 'wiki/' + self.toy.wiki_slug + "/")
ending_location = resp.redirect_chain[-1][0] ending_location = resp.redirect_chain[-1][0]
ending_status = resp.redirect_chain[-1][1] ending_status = resp.redirect_chain[-1][1]
......
...@@ -303,7 +303,7 @@ class TestViewAuth(PageLoader): ...@@ -303,7 +303,7 @@ class TestViewAuth(PageLoader):
'instructor_dashboard', 'instructor_dashboard',
'gradebook', 'gradebook',
'grade_summary',)] 'grade_summary',)]
urls.append(reverse('student_profile', kwargs={'course_id': course.id, urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id})) 'student_id': user(self.student).id}))
return urls return urls
...@@ -388,7 +388,7 @@ class TestViewAuth(PageLoader): ...@@ -388,7 +388,7 @@ class TestViewAuth(PageLoader):
list of urls that students should be able to see only list of urls that students should be able to see only
after launch, but staff should see before after launch, but staff should see before
""" """
urls = reverse_urls(['info', 'courseware', 'profile'], course) urls = reverse_urls(['info', 'courseware', 'progress'], course)
urls.extend([ urls.extend([
reverse('book', kwargs={'course_id': course.id, 'book_index': book.title}) reverse('book', kwargs={'course_id': course.id, 'book_index': book.title})
for book in course.textbooks for book in course.textbooks
...@@ -411,7 +411,7 @@ class TestViewAuth(PageLoader): ...@@ -411,7 +411,7 @@ class TestViewAuth(PageLoader):
"""list of urls that only instructors/staff should be able to see""" """list of urls that only instructors/staff should be able to see"""
urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'], urls = reverse_urls(['instructor_dashboard','gradebook','grade_summary'],
course) course)
urls.append(reverse('student_profile', kwargs={'course_id': course.id, urls.append(reverse('student_progress', kwargs={'course_id': course.id,
'student_id': user(self.student).id})) 'student_id': user(self.student).id}))
return urls return urls
......
...@@ -258,11 +258,10 @@ def university_profile(request, org_id): ...@@ -258,11 +258,10 @@ def university_profile(request, org_id):
@login_required @login_required
@cache_control(no_cache=True, no_store=True, must_revalidate=True) @cache_control(no_cache=True, no_store=True, must_revalidate=True)
def profile(request, course_id, student_id=None): def progress(request, course_id, student_id=None):
""" User profile. Show username, location, etc, as well as grades . """ User progress. We show the grade bar and every problem score.
We need to allow the user to change some of these settings.
Course staff are allowed to see the profiles of students in their class. Course staff are allowed to see the progress of students in their class.
""" """
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
staff_access = has_access(request.user, course, 'staff') staff_access = has_access(request.user, course, 'staff')
...@@ -276,28 +275,20 @@ def profile(request, course_id, student_id=None): ...@@ -276,28 +275,20 @@ def profile(request, course_id, student_id=None):
raise Http404 raise Http404
student = User.objects.get(id=int(student_id)) student = User.objects.get(id=int(student_id))
user_info = UserProfile.objects.get(user=student)
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course) student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(request.user, course)
course_module = get_module(request.user, request, course.location, student_module_cache, course_id=course_id) course_module = get_module(request.user, request, course.location, student_module_cache, course_id=course_id)
courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache) courseware_summary = grades.progress_summary(student, course_module, course.grader, student_module_cache)
grade_summary = grades.grade(request.user, request, course, student_module_cache) grade_summary = grades.grade(request.user, request, course, student_module_cache)
context = {'name': user_info.name, context = {'course': course,
'username': student.username,
'location': user_info.location,
'language': user_info.language,
'email': student.email,
'course': course,
'csrf': csrf(request)['csrf_token'],
'courseware_summary': courseware_summary, 'courseware_summary': courseware_summary,
'grade_summary': grade_summary, 'grade_summary': grade_summary,
'staff_access': staff_access, 'staff_access': staff_access,
} }
context.update() context.update()
return render_to_response('profile.html', context) return render_to_response('progress.html', context)
......
...@@ -53,6 +53,18 @@ input[type="password"] { ...@@ -53,6 +53,18 @@ input[type="password"] {
} }
} }
input[type="reset"],
input[type="submit"],
input[type="button"],
button,
.button {
@extend .gray-button;
form & {
@extend .gray-button;
}
}
img { img {
max-width: 100%; max-width: 100%;
......
...@@ -6,28 +6,34 @@ h1.top-header { ...@@ -6,28 +6,34 @@ h1.top-header {
padding-bottom: lh(); padding-bottom: lh();
} }
.light-button, a.light-button { .button-reset {
border: 1px solid #ccc;
@include border-radius(3px);
@include box-shadow(inset 0 1px 0 #fff);
color: #666;
cursor: pointer;
font: 400 $body-font-size $body-font-family;
@include linear-gradient(#fff, lighten(#888, 40%));
padding: 4px 8px;
text-decoration: none;
text-shadow: none;
text-transform: none; text-transform: none;
letter-spacing: 0; letter-spacing: 0;
-webkit-font-smoothing: antialiased;
&:hover, &:focus { &:hover {
border: 1px solid #ccc;
@include linear-gradient(#fff, lighten(#888, 37%));
text-decoration: none; text-decoration: none;
} }
} }
.light-button, a.light-button, // only used in askbot as classes
.gray-button {
@include button(simple, #eee);
@extend .button-reset;
font-size: em(13);
}
.blue-button {
@include button(simple, $blue);
@extend .button-reset;
font-size: em(13);
}
.pink-button {
@include button(simple, $pink);
@extend .button-reset;
font-size: em(13);
}
.content { .content {
@include box-sizing(border-box); @include box-sizing(border-box);
display: table-cell; display: table-cell;
......
...@@ -43,8 +43,8 @@ div.discussion-wrapper aside { ...@@ -43,8 +43,8 @@ div.discussion-wrapper aside {
width: 27%; width: 27%;
float: right; float: right;
text-align: center; text-align: center;
padding-left: 0; padding: 4px 0;
padding-right: 0; text-transform: capitalize;
} }
input[type="text"] { input[type="text"] {
...@@ -300,7 +300,7 @@ div.discussion-wrapper aside { ...@@ -300,7 +300,7 @@ div.discussion-wrapper aside {
border-top: 0; border-top: 0;
a { a {
@extend .light-button; @extend .gray-button;
@include box-sizing(border-box); @include box-sizing(border-box);
display: block; display: block;
text-align: center; text-align: center;
......
...@@ -33,7 +33,7 @@ def url_class(url): ...@@ -33,7 +33,7 @@ def url_class(url):
<li class="wiki"><a href="${reverse('course_wiki', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li> <li class="wiki"><a href="${reverse('course_wiki', args=[course.id])}" class="${url_class('wiki')}">Wiki</a></li>
% endif % endif
% if user.is_authenticated(): % if user.is_authenticated():
<li class="profile"><a href="${reverse('profile', args=[course.id])}" class="${url_class('profile')}">Profile</a></li> <li class="profile"><a href="${reverse('progress', args=[course.id])}" class="${url_class('progress')}">Progress</a></li>
% endif % endif
% if staff_access: % if staff_access:
<li class="instructor"><a href="${reverse('instructor_dashboard', args=[course.id])}" class="${url_class('instructor')}">Instructor</a></li> <li class="instructor"><a href="${reverse('instructor_dashboard', args=[course.id])}" class="${url_class('instructor')}">Instructor</a></li>
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
%for student in students: %for student in students:
<tr> <tr>
<td> <td>
<a href="${reverse('student_profile', kwargs=dict(course_id=course_id, student_id=student['id']))}">${student['username']}</a> <a href="${reverse('student_progress', kwargs=dict(course_id=course_id, student_id=student['id']))}">${student['username']}</a>
</td> </td>
</tr> </tr>
%endfor %endfor
......
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
<%block name="headextra">
<%static:css group='course'/>
</%block>
<%namespace name="profile_graphs" file="profile_graphs.js"/>
<%block name="title"><title>Profile - edX 6.002x</title></%block>
<%!
from django.core.urlresolvers import reverse
%>
<%block name="js_extra">
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.symbol.js')}"></script>
<script>
${profile_graphs.body(grade_summary, course.grade_cutoffs, "grade-detail-graph")}
</script>
<script>
var loc=true; // Activate on clicks? Not if already clicked.
var lang=true;
$(function() {
$("#change_location").click(function() {
$(this).hide();
log_event("profile", {"type":"location_show", "old":$("#location_sub").text()});
if(loc) {
$("#description").html('<div>'+
"Preferred format is city, state, country (so for us, "+
"&quot;Cambridge, Massachusetts, USA&quot;), but give "+
"as much or as little detail as you want. </div>");
loc=false;
$("#location_sub").html('<form>'+'<input id="id_loc_text" type="text" name="loc_text" />'+
'<input type="submit" id="change_loc_button" value="Save" />'+'</form>');
$("#change_loc_button").click(function() {
$("#change_location").show();
postJSON('/change_setting', {'location':$("#id_loc_text").attr("value")}, function(json) {
$("#location_sub").text(json.location);
loc=true;
$("#description").html("");
log_event("profile", {"type":"location_change", "new":json.location});
});
});
}
});
$('#change_password').click(function(){
$('.modal').trigger('click');
log_event("profile", {"type":"password_show"});
});
$('#pwd_reset_button').click(function() {
$.postWithPrefix('/password_reset/',{ "csrfmiddlewaretoken" : "${ csrf }",
"email" : $('#id_email').val()}, function(data){
$("#password_reset_complete_link").click();
log_event("profile", {"type":"password_send"});
});
});
$("#change_email_form").submit(function(){
var new_email = $('#new_email_field').val();
var new_password = $('#new_email_password').val();
postJSON('/change_email',{"new_email":new_email,
"password":new_password},
function(data){
if(data.success){
$("#change_email").html("<h1>Please verify your new email</h1><p>You'll receive a confirmation in your in-box. Please click the link in the email to confirm the email change.</p>");
} else {
$("#change_email_error").html(data.error);
}
});
log_event("profile", {"type":"email_change_request",
"old_email":"${email}",
"new_email":new_email});
return false;
});
$("#change_name_form").submit(function(){
var new_name = $('#new_name_field').val();
var rationale = $('#name_rationale_field').val();
postJSON('/change_name',{"new_name":new_name,
"rationale":rationale},
function(data){
if(data.success){
$("#apply_name_change").html("<h1>Your request has been submitted.</h1><p>We'll send you an e-mail when approve the change or need further information. Please allow for up to a week for us to process your request.</p>");
} else {
$("#change_name_error").html(data.error);
}
});
log_event("profile", {"type":"name_change_request",
"new_name":new_name,
"rationale":rationale});
return false;
});
});
</script>
</%block>
<%include file="course_navigation.html" args="active_page='profile'" />
<section class="container">
<div class="profile-wrapper">
<section class="course-info">
<header>
<h1>Course Progress</h1>
</header>
<div id="grade-detail-graph"></div>
<ol class="chapters">
%for chapter in courseware_summary:
%if not chapter['display_name'] == "hidden":
<li>
<h2>${ chapter['display_name'] }</h2>
<ol class="sections">
%for section in chapter['sections']:
<li>
<%
earned = section['section_total'].earned
total = section['section_total'].possible
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
%>
<h3><a href="${reverse('courseware_section', kwargs=dict(course_id=course.id, chapter=chapter['url_name'], section=section['url_name']))}">
${ section['display_name'] }</a><span> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</span></h3>
<p>
${section['format']}
%if 'due' in section and section['due']!="":
<em>
due ${section['due']}
</em>
%endif
</p>
%if len(section['scores']) > 0:
<section class="scores">
<h3> ${ "Problem Scores: " if section['graded'] else "Practice Scores: "} </h3>
<ol>
%for score in section['scores']:
<li>${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}</li>
%endfor
</ol>
</section>
%endif
</li> <!--End section-->
%endfor
</ol> <!--End sections-->
</li> <!--End chapter-->
%endif
%endfor
</ol> <!--End chapters-->
</section>
<section aria-label="Profile Navigation" class="user-info">
<header>
<h1>Student Profile</h1>
</header>
<ul>
<li>
Name: <strong>${name}</strong>
%if True:
<a href="#apply_name_change" rel="leanModal" class="name-edit" aria-label="Edit Name">Edit</a>
%else:
(Name change pending)
%endif
</li>
<li>
Forum name: <strong>${username}</strong>
</li>
<li>
E-mail: <strong>${email}</strong> <a href="#change_email" rel="leanModal" class="edit-email" aria-label="Edit Email">Edit</a>
</li>
<li>
Location: <div id="location_sub">${location}</div><div id="description"></div> <a href="#" id="change_location" aria-label="Edit Location">Edit</a>
</li>
<li>
Password reset
<input id="id_email" type="hidden" name="email" maxlength="75" value="${email}" />
<input type="submit" id="pwd_reset_button" value="Reset" aria-label="Reset Password" />
<p>We'll e-mail a password reset link to ${email}.</p>
</li>
</ul>
</section>
</div>
</section>
<div id="password_reset_complete" class="modal">
<a href="#password_reset_complete" rel="leanModal" id="password_reset_complete_link"></a>
<h1>Password Reset Email Sent</h1>
<p>
An email has been sent to ${email}. Follow the link in the email to change your password.
</p>
</div>
<div id="apply_name_change" class="modal">
<div class="inner-wrapper">
<header>
<h2>Apply to change your name</h2>
<hr />
</header>
<form id="change_name_form">
<div id="change_name_error"> </div>
<fieldset>
<p>To uphold the credibility of <span class="edx">edX</span> certificates, name changes must go through an approval process. A member of the course staff will review your request, and if approved, update your information. Please allow up to a week for your request to be processed. Thank you.</p>
<label>Enter your desired full name, as it will appear on the <span class="edx">edX</span> Certificate: </label>
<input id="new_name_field" value="" type="text" />
<label>Reason for name change:</label>
<textarea id="name_rationale_field" value=""></textarea>
<input type="submit" id="submit">
</fieldset>
</form>
</div>
</div>
<div id="change_email" class="modal">
<div class="inner-wrapper">
<header>
<h2>Change e-mail</h2>
<hr />
</header>
<div id="apply_name_change_error"></div>
<form id="change_email_form">
<div id="change_email_error"> </div>
<fieldset>
<label> Please enter your new email address: </label>
<input id="new_email_field" type="email" value="" />
<label> Please confirm your password: </label>
<input id="new_email_password" value="" type="password" />
<p>We will send a confirmation to both ${email} and your new e-mail as part of the process.</p>
<input type="submit" id="submit_email_change" />
</fieldset>
</form>
</div>
</div>
<div id="deactivate-account" class="modal">
<div class="inner-wrapper">
<header>
<h2>Deactivate <span class="edx">edX</span> Account</h2>
<hr />
</header>
<p>Once you deactivate you&rsquo;re MIT<em>x</em> account you will no longer recieve updates and new class announcements from MIT<em>x</em>.</p>
<p>If you&rsquo;d like to still get updates and new class announcements you can just <a href="#unenroll" rel="leanModal">unenroll</a> and keep your account active.</p>
<form id="unenroll_form">
<div id="unenroll_error"> </div>
<fieldset>
<input type="submit" id="" value="Yes, I don't want an edX account or hear about any new classes or updates to edX" />
</fieldset>
</form>
</div>
</div>
<div id="unenroll" class="modal">
<div class="inner-wrapper">
<header>
<h2>Unenroll from 6.002x</h2>
<hr />
</header>
<p>Please note: you will still receive updates and new class announcements from ed<em>X</em>. If you don&rsquo;t wish to receive any more updates or announcements <a href="#deactivate-account" rel="leanModal">deactivate your account</a>.</p>
<form id="unenroll_form">
<div id="unenroll_error"> </div>
<fieldset>
<input type="submit" id="" value="Yes, I want to unenroll from 6.002x but still hear about any new classes or updates to edX" />
</fieldset>
</form>
</div>
</div>
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
<%block name="headextra">
<%static:css group='course'/>
</%block>
<%namespace name="profile_graphs" file="profile_graphs.js"/>
<%block name="title"><title>Progress - edX 6.002x</title></%block>
<%!
from django.core.urlresolvers import reverse
%>
<%block name="js_extra">
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.symbol.js')}"></script>
<script>
${profile_graphs.body(grade_summary, course.grade_cutoffs, "grade-detail-graph")}
</script>
</%block>
<%include file="course_navigation.html" args="active_page='progress'" />
<section class="container">
<div class="profile-wrapper">
<section class="course-info">
<header>
<h1>Course Progress</h1>
</header>
<div id="grade-detail-graph"></div>
<ol class="chapters">
%for chapter in courseware_summary:
%if not chapter['display_name'] == "hidden":
<li>
<h2>${ chapter['display_name'] }</h2>
<ol class="sections">
%for section in chapter['sections']:
<li>
<%
earned = section['section_total'].earned
total = section['section_total'].possible
percentageString = "{0:.0%}".format( float(earned)/total) if earned > 0 and total > 0 else ""
%>
<h3><a href="${reverse('courseware_section', kwargs=dict(course_id=course.id, chapter=chapter['url_name'], section=section['url_name']))}">
${ section['display_name'] }</a><span> ${"({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )}</span></h3>
<p>
${section['format']}
%if 'due' in section and section['due']!="":
<em>
due ${section['due']}
</em>
%endif
</p>
%if len(section['scores']) > 0:
<section class="scores">
<h3> ${ "Problem Scores: " if section['graded'] else "Practice Scores: "} </h3>
<ol>
%for score in section['scores']:
<li>${"{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))}</li>
%endfor
</ol>
</section>
%endif
</li> <!--End section-->
%endfor
</ol> <!--End sections-->
</li> <!--End chapter-->
%endif
%endfor
</ol> <!--End chapters-->
</section>
<section aria-label="Profile Navigation" class="user-info">
</section>
</div>
</section>
...@@ -138,11 +138,11 @@ if settings.COURSEWARE_ENABLED: ...@@ -138,11 +138,11 @@ if settings.COURSEWARE_ENABLED:
'courseware.views.index', name="courseware_chapter"), 'courseware.views.index', name="courseware_chapter"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/courseware/(?P<chapter>[^/]*)/(?P<section>[^/]*)/$',
'courseware.views.index', name="courseware_section"), 'courseware.views.index', name="courseware_section"),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/progress$',
'courseware.views.profile', name="profile"), 'courseware.views.progress', name="progress"),
# Takes optional student_id for instructor use--shows profile as that student sees it. # Takes optional student_id for instructor use--shows profile as that student sees it.
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/profile/(?P<student_id>[^/]*)/$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/progress/(?P<student_id>[^/]*)/$',
'courseware.views.profile', name="student_profile"), 'courseware.views.progress', name="student_progress"),
# For the instructor # For the instructor
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/instructor$',
......
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