Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
9e8ec09b
Commit
9e8ec09b
authored
Sep 02, 2012
by
ichuang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
pep8 fixes
parent
0ddd8949
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
65 additions
and
49 deletions
+65
-49
common/djangoapps/student/models.py
+27
-9
lms/djangoapps/instructor/views.py
+38
-40
No files found.
common/djangoapps/student/models.py
View file @
9e8ec09b
...
...
@@ -4,7 +4,7 @@ Models for Student Information
Replication Notes
In our live deployment, we intend to run in a scenario where there is a pool of
Portal servers that hold the canoncial user information and that user
Portal servers that hold the canoncial user information and that user
information is replicated to slave Course server pools. Each Course has a set of
servers that serves only its content and has users that are relevant only to it.
...
...
@@ -61,6 +61,7 @@ from xmodule.modulestore.django import modulestore
log
=
logging
.
getLogger
(
__name__
)
class
UserProfile
(
models
.
Model
):
"""This is where we store all the user demographic fields. We have a
separate table for this rather than extending the built-in Django auth_user.
...
...
@@ -175,6 +176,7 @@ class PendingEmailChange(models.Model):
new_email
=
models
.
CharField
(
blank
=
True
,
max_length
=
255
,
db_index
=
True
)
activation_key
=
models
.
CharField
((
'activation key'
),
max_length
=
32
,
unique
=
True
,
db_index
=
True
)
class
CourseEnrollment
(
models
.
Model
):
user
=
models
.
ForeignKey
(
User
)
course_id
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
...
...
@@ -185,7 +187,8 @@ class CourseEnrollment(models.Model):
unique_together
=
((
'user'
,
'course_id'
),
)
def
__unicode__
(
self
):
return
"
%
s:
%
s (
%
s)"
%
(
self
.
user
,
self
.
course_id
,
self
.
created
)
return
"
%
s:
%
s (
%
s)"
%
(
self
.
user
,
self
.
course_id
,
self
.
created
)
@receiver
(
post_save
,
sender
=
CourseEnrollment
)
def
assign_default_role
(
sender
,
instance
,
**
kwargs
):
...
...
@@ -276,6 +279,7 @@ def add_user_to_default_group(user, group):
utg
.
users
.
add
(
User
.
objects
.
get
(
username
=
user
))
utg
.
save
()
@receiver
(
post_save
,
sender
=
User
)
def
update_user_information
(
sender
,
instance
,
created
,
**
kwargs
):
try
:
...
...
@@ -286,6 +290,7 @@ def update_user_information(sender, instance, created, **kwargs):
log
.
error
(
unicode
(
e
))
log
.
error
(
"update user info to discussion failed for user with id: "
+
str
(
instance
.
id
))
########################## REPLICATION SIGNALS #################################
# @receiver(post_save, sender=User)
def
replicate_user_save
(
sender
,
**
kwargs
):
...
...
@@ -295,6 +300,7 @@ def replicate_user_save(sender, **kwargs):
for
course_db_name
in
db_names_to_replicate_to
(
user_obj
.
id
):
replicate_user
(
user_obj
,
course_db_name
)
# @receiver(post_save, sender=CourseEnrollment)
def
replicate_enrollment_save
(
sender
,
**
kwargs
):
"""This is called when a Student enrolls in a course. It has to do the
...
...
@@ -320,12 +326,14 @@ def replicate_enrollment_save(sender, **kwargs):
log
.
debug
(
"Replicating user profile because of new enrollment"
)
user_profile
=
UserProfile
.
objects
.
get
(
user_id
=
enrollment_obj
.
user_id
)
replicate_model
(
UserProfile
.
save
,
user_profile
,
enrollment_obj
.
user_id
)
# @receiver(post_delete, sender=CourseEnrollment)
def
replicate_enrollment_delete
(
sender
,
**
kwargs
):
enrollment_obj
=
kwargs
[
'instance'
]
return
replicate_model
(
CourseEnrollment
.
delete
,
enrollment_obj
,
enrollment_obj
.
user_id
)
# @receiver(post_save, sender=UserProfile)
def
replicate_userprofile_save
(
sender
,
**
kwargs
):
"""We just updated the UserProfile (say an update to the name), so push that
...
...
@@ -333,12 +341,13 @@ def replicate_userprofile_save(sender, **kwargs):
user_profile_obj
=
kwargs
[
'instance'
]
return
replicate_model
(
UserProfile
.
save
,
user_profile_obj
,
user_profile_obj
.
user_id
)
######### Replication functions #########
USER_FIELDS_TO_COPY
=
[
"id"
,
"username"
,
"first_name"
,
"last_name"
,
"email"
,
"password"
,
"is_staff"
,
"is_active"
,
"is_superuser"
,
"last_login"
,
"date_joined"
]
def
replicate_user
(
portal_user
,
course_db_name
):
"""Replicate a User to the correct Course DB. This is more complicated than
it should be because Askbot extends the auth_user table and adds its own
...
...
@@ -362,9 +371,10 @@ def replicate_user(portal_user, course_db_name):
course_user
.
save
(
using
=
course_db_name
)
unmark
(
course_user
)
def
replicate_model
(
model_method
,
instance
,
user_id
):
"""
model_method is the model action that we want replicated. For instance,
model_method is the model action that we want replicated. For instance,
UserProfile.save
"""
if
not
should_replicate
(
instance
):
...
...
@@ -379,8 +389,10 @@ def replicate_model(model_method, instance, user_id):
model_method
(
instance
,
using
=
db_name
)
unmark
(
instance
)
######### Replication Helpers #########
def
is_valid_course_id
(
course_id
):
"""Right now, the only database that's not a course database is 'default'.
I had nicer checking in here originally -- it would scan the courses that
...
...
@@ -390,26 +402,30 @@ def is_valid_course_id(course_id):
"""
return
course_id
!=
'default'
def
is_portal
():
"""Are we in the portal pool? Only Portal servers are allowed to replicate
their changes. For now, only Portal servers see multiple DBs, so we use
that to decide."""
return
len
(
settings
.
DATABASES
)
>
1
def
db_names_to_replicate_to
(
user_id
):
"""Return a list of DB names that this user_id is enrolled in."""
return
[
c
.
course_id
for
c
in
CourseEnrollment
.
objects
.
filter
(
user_id
=
user_id
)
if
is_valid_course_id
(
c
.
course_id
)]
def
marked_handled
(
instance
):
"""Have we marked this instance as being handled to avoid infinite loops
caused by saving models in post_save hooks for the same models?"""
return
hasattr
(
instance
,
'_do_not_copy_to_course_db'
)
and
instance
.
_do_not_copy_to_course_db
def
mark_handled
(
instance
):
"""You have to mark your instance with this function or else we'll go into
an infinite loop since we're putting listeners on Model saves/deletes and
an infinite loop since we're putting listeners on Model saves/deletes and
the act of replication requires us to call the same model method.
We create a _replicated attribute to differentiate the first save of this
...
...
@@ -418,16 +434,18 @@ def mark_handled(instance):
"""
instance
.
_do_not_copy_to_course_db
=
True
def
unmark
(
instance
):
"""If we don't unmark a model after we do replication, then consecutive
"""If we don't unmark a model after we do replication, then consecutive
save() calls won't be properly replicated."""
instance
.
_do_not_copy_to_course_db
=
False
def
should_replicate
(
instance
):
"""Should this instance be replicated? We need to be a Portal server and
the instance has to not have been marked_handled."""
if
marked_handled
(
instance
):
# Basically, avoid an infinite loop. You should
# Basically, avoid an infinite loop. You should
log
.
debug
(
"{0} should not be replicated because it's been marked"
.
format
(
instance
))
return
False
...
...
lms/djangoapps/instructor/views.py
View file @
9e8ec09b
...
...
@@ -38,54 +38,55 @@ log = logging.getLogger("mitx.courseware")
template_imports
=
{
'urllib'
:
urllib
}
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
def
instructor_dashboard
(
request
,
course_id
):
"""Display the instructor dashboard for a course."""
course
=
get_course_with_access
(
request
.
user
,
course_id
,
'staff'
)
instructor_access
=
has_access
(
request
.
user
,
course
,
'instructor'
)
# an instructor can manage staff lists
instructor_access
=
has_access
(
request
.
user
,
course
,
'instructor'
)
# an instructor can manage staff lists
msg
=
''
# msg += ('POST=%s' % dict(request.POST)).replace('<','<')
def
escape
(
s
):
"""escape HTML special characters in string"""
return
str
(
s
)
.
replace
(
'<'
,
'<'
)
.
replace
(
'>'
,
'>'
)
return
str
(
s
)
.
replace
(
'<'
,
'<'
)
.
replace
(
'>'
,
'>'
)
# assemble some course statistics for output to instructor
datatable
=
{
'header'
:
[
'Statistic'
,
'Value'
],
datatable
=
{
'header'
:
[
'Statistic'
,
'Value'
],
'title'
:
'Course Statistics At A Glance'
,
}
data
=
[
[
'# Enrolled'
,
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
)
.
count
()]
]
data
=
[
[
'# Enrolled'
,
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
)
.
count
()]
]
data
+=
compute_course_stats
(
course
)
.
items
()
if
request
.
user
.
is_staff
:
data
.
append
([
'metadata'
,
escape
(
str
(
course
.
metadata
))])
datatable
[
'data'
]
=
data
def
return_csv
(
fn
,
datatable
):
def
return_csv
(
fn
,
datatable
):
response
=
HttpResponse
(
mimetype
=
'text/csv'
)
response
[
'Content-Disposition'
]
=
'attachment; filename=
%
s'
%
fn
writer
=
csv
.
writer
(
response
,
dialect
=
'excel'
,
quotechar
=
'"'
,
quoting
=
csv
.
QUOTE_ALL
)
writer
=
csv
.
writer
(
response
,
dialect
=
'excel'
,
quotechar
=
'"'
,
quoting
=
csv
.
QUOTE_ALL
)
writer
.
writerow
(
datatable
[
'header'
])
for
datarow
in
datatable
[
'data'
]:
writer
.
writerow
(
datarow
)
return
response
def
get_staff_group
(
course
):
staffgrp
=
get_access_group_name
(
course
,
'staff'
)
staffgrp
=
get_access_group_name
(
course
,
'staff'
)
try
:
group
=
Group
.
objects
.
get
(
name
=
staffgrp
)
except
Group
.
DoesNotExist
:
group
=
Group
(
name
=
staffgrp
)
# create the group
group
=
Group
(
name
=
staffgrp
)
# create the group
group
.
save
()
return
group
# process actions from form POST
action
=
request
.
POST
.
get
(
'action'
,
''
)
action
=
request
.
POST
.
get
(
'action'
,
''
)
if
'Reload'
in
action
:
log
.
debug
(
'reloading
%
s (
%
s)'
%
(
course_id
,
course
))
log
.
debug
(
'reloading
%
s (
%
s)'
%
(
course_id
,
course
))
try
:
data_dir
=
course
.
metadata
[
'data_dir'
]
modulestore
()
.
try_load_course
(
data_dir
)
...
...
@@ -93,7 +94,7 @@ def instructor_dashboard(request, course_id):
except
Exception
as
err
:
msg
+=
'<br/><p>Error:
%
s</p>'
%
escape
(
err
)
elif
action
==
'Dump list of enrolled students'
:
elif
action
==
'Dump list of enrolled students'
:
log
.
debug
(
action
)
datatable
=
get_student_grade_summary_data
(
request
,
course
,
course_id
,
get_grades
=
False
)
datatable
[
'title'
]
=
'List of students enrolled in
%
s'
%
course_id
...
...
@@ -122,11 +123,11 @@ def instructor_dashboard(request, course_id):
msg
+=
'Staff group =
%
s'
%
group
.
name
log
.
debug
(
'staffgrp=
%
s'
%
group
.
name
)
uset
=
group
.
user_set
.
all
()
datatable
=
{
'header'
:
[
'Username'
,
'Full name'
]}
datatable
[
'data'
]
=
[[
x
.
username
,
x
.
profile
.
name
]
for
x
in
uset
]
datatable
=
{
'header'
:
[
'Username'
,
'Full name'
]}
datatable
[
'data'
]
=
[[
x
.
username
,
x
.
profile
.
name
]
for
x
in
uset
]
datatable
[
'title'
]
=
'List of Staff in course
%
s'
%
course_id
elif
action
==
'Add course staff'
:
elif
action
==
'Add course staff'
:
uname
=
request
.
POST
[
'staffuser'
]
try
:
user
=
User
.
objects
.
get
(
username
=
uname
)
...
...
@@ -135,11 +136,11 @@ def instructor_dashboard(request, course_id):
user
=
None
if
user
is
not
None
:
group
=
get_staff_group
(
course
)
msg
+=
'<font color="green">Added
%
s to staff group =
%
s</font>'
%
(
user
,
group
.
name
)
msg
+=
'<font color="green">Added
%
s to staff group =
%
s</font>'
%
(
user
,
group
.
name
)
log
.
debug
(
'staffgrp=
%
s'
%
group
.
name
)
user
.
groups
.
add
(
group
)
elif
action
==
'Remove course staff'
:
elif
action
==
'Remove course staff'
:
uname
=
request
.
POST
[
'staffuser'
]
try
:
user
=
User
.
objects
.
get
(
username
=
uname
)
...
...
@@ -148,21 +149,22 @@ def instructor_dashboard(request, course_id):
user
=
None
if
user
is
not
None
:
group
=
get_staff_group
(
course
)
msg
+=
'<font color="green">Removed
%
s from staff group =
%
s</font>'
%
(
user
,
group
.
name
)
msg
+=
'<font color="green">Removed
%
s from staff group =
%
s</font>'
%
(
user
,
group
.
name
)
log
.
debug
(
'staffgrp=
%
s'
%
group
.
name
)
user
.
groups
.
remove
(
group
)
# For now, mostly a static page
context
=
{
'course'
:
course
,
'staff_access'
:
True
,
'admin_access'
:
request
.
user
.
is_staff
,
'instructor_access'
:
instructor_access
,
'datatable'
:
datatable
,
'msg'
:
msg
,
'admin_access'
:
request
.
user
.
is_staff
,
'instructor_access'
:
instructor_access
,
'datatable'
:
datatable
,
'msg'
:
msg
,
}
return
render_to_response
(
'courseware/instructor_dashboard.html'
,
context
)
def
get_student_grade_summary_data
(
request
,
course
,
course_id
,
get_grades
=
True
,
get_raw_scores
=
False
):
'''
Return data arrays with student identity and grades for specified course.
...
...
@@ -171,7 +173,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
course_id = course ID
Note: both are passed in, only because instructor_dashboard already has them already.
returns datatable = dict(header=header, data=data)
where
...
...
@@ -183,9 +185,10 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
'''
enrolled_students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_id
)
.
order_by
(
'username'
)
header
=
[
'ID'
,
'Username'
,
'Full Name'
,
'edX email'
,
'External email'
]
header
=
[
'ID'
,
'Username'
,
'Full Name'
,
'edX email'
,
'External email'
]
if
get_grades
:
gradeset
=
grades
.
grade
(
enrolled_students
[
0
],
request
,
course
,
keep_raw_scores
=
get_raw_scores
)
# just to construct the header
# just to construct the header
gradeset
=
grades
.
grade
(
enrolled_students
[
0
],
request
,
course
,
keep_raw_scores
=
get_raw_scores
)
# log.debug('student %s gradeset %s' % (enrolled_students[0], gradeset))
if
get_raw_scores
:
header
+=
[
score
.
section
for
score
in
gradeset
[
'raw_scores'
]]
...
...
@@ -196,7 +199,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
data
=
[]
for
student
in
enrolled_students
:
datarow
=
[
student
.
id
,
student
.
username
,
student
.
profile
.
name
,
student
.
email
]
datarow
=
[
student
.
id
,
student
.
username
,
student
.
profile
.
name
,
student
.
email
]
try
:
datarow
.
append
(
student
.
externalauthmap
.
external_email
)
except
:
# ExternalAuthMap.DoesNotExist
...
...
@@ -214,6 +217,7 @@ def get_student_grade_summary_data(request, course, course_id, get_grades=True,
datatable
[
'data'
]
=
data
return
datatable
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
def
gradebook
(
request
,
course_id
):
"""
...
...
@@ -240,7 +244,7 @@ def gradebook(request, course_id):
'course'
:
course
,
'course_id'
:
course_id
,
# Checked above
'staff_access'
:
True
,})
'staff_access'
:
True
,
})
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
...
...
@@ -250,9 +254,10 @@ def grade_summary(request, course_id):
# For now, just a static page
context
=
{
'course'
:
course
,
'staff_access'
:
True
,}
'staff_access'
:
True
,
}
return
render_to_response
(
'courseware/grade_summary.html'
,
context
)
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
def
enroll_students
(
request
,
course_id
):
...
...
@@ -269,7 +274,7 @@ def enroll_students(request, course_id):
'''
course
=
get_course_with_access
(
request
.
user
,
course_id
,
'staff'
)
existing_students
=
[
ce
.
user
.
email
for
ce
in
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
)]
existing_students
=
[
ce
.
user
.
email
for
ce
in
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_id
)]
if
'new_students'
in
request
.
POST
:
new_students
=
request
.
POST
[
'new_students'
]
.
split
(
'
\n
'
)
...
...
@@ -282,20 +287,21 @@ def enroll_students(request, course_id):
for
student
in
new_students
:
try
:
nce
=
CourseEnrollment
(
user
=
User
.
objects
.
get
(
email
=
student
),
course_id
=
course_id
)
nce
=
CourseEnrollment
(
user
=
User
.
objects
.
get
(
email
=
student
),
course_id
=
course_id
)
nce
.
save
()
added_students
.
append
(
student
)
except
:
rejected_students
.
append
(
student
)
return
render_to_response
(
"enroll_students.html"
,
{
'course'
:
course_id
,
return
render_to_response
(
"enroll_students.html"
,
{
'course'
:
course_id
,
'existing_students'
:
existing_students
,
'added_students'
:
added_students
,
'rejected_students'
:
rejected_students
,
'debug'
:
new_students
})
'debug'
:
new_students
})
#-----------------------------------------------------------------------------
def
compute_course_stats
(
course
):
'''
Compute course statistics, including number of problems, videos, html.
...
...
@@ -308,23 +314,15 @@ def compute_course_stats(course):
counts
=
defaultdict
(
int
)
print
"hello world"
def
walk
(
module
):
children
=
module
.
get_children
()
if
not
children
:
category
=
module
.
__class__
.
__name__
# HtmlDescriptor, CapaDescriptor, ...
category
=
module
.
__class__
.
__name__
# HtmlDescriptor, CapaDescriptor, ...
counts
[
category
]
+=
1
return
for
c
in
children
:
# print c.__class__.__name__
walk
(
c
)
walk
(
course
)
print
"course
%
s counts=
%
s"
%
(
course
.
display_name
,
counts
)
stats
=
dict
(
counts
)
# number of each kind of module
return
stats
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment