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
b2c6a534
Commit
b2c6a534
authored
Sep 22, 2017
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Grades cleanup: remove no longer needed management commands
EDUCATOR-178
parent
5c88600f
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
2 additions
and
314 deletions
+2
-314
lms/djangoapps/grades/management/commands/get_grades.py
+0
-136
lms/djangoapps/grades/management/commands/reset_grades.py
+0
-144
lms/djangoapps/grades/management/commands/tests/test_reset_grades.py
+0
-0
lms/djangoapps/grades/models.py
+2
-34
No files found.
lms/djangoapps/grades/management/commands/get_grades.py
deleted
100644 → 0
View file @
5c88600f
"""
Management command to generate a list of grades for
all students that are enrolled in a course.
"""
import
csv
import
datetime
import
os
from
optparse
import
make_option
from
django.contrib.auth.models
import
User
from
django.core.handlers.base
import
BaseHandler
from
django.core.management.base
import
BaseCommand
,
CommandError
from
django.test.client
import
RequestFactory
from
opaque_keys.edx.keys
import
CourseKey
from
lms.djangoapps.certificates.models
import
GeneratedCertificate
from
lms.djangoapps.courseware
import
courses
from
lms.djangoapps.grades.new.course_grade_factory
import
CourseGradeFactory
class
RequestMock
(
RequestFactory
):
"""
Class to create a mock request.
"""
def
request
(
self
,
**
request
):
"Construct a generic request object."
request
=
RequestFactory
.
request
(
self
,
**
request
)
handler
=
BaseHandler
()
handler
.
load_middleware
()
for
middleware_method
in
handler
.
_request_middleware
:
# pylint: disable=protected-access
if
middleware_method
(
request
):
raise
Exception
(
"Couldn't create request mock object - "
"request middleware returned a response"
)
return
request
class
Command
(
BaseCommand
):
"""
Management command for get_grades
"""
help
=
"""
Generate a list of grades for all students
that are enrolled in a course.
CSV will include the following:
- username
- email
- grade in the certificate table if it exists
- computed grade
- grade breakdown
Outputs grades to a csv file.
Example:
sudo -u www-data SERVICE_VARIANT=lms /opt/edx/bin/django-admin.py get_grades
\
-c MITx/Chi6.00intro/A_Taste_of_Python_Programming -o /tmp/20130813-6.00x.csv
\
--settings=lms.envs.aws --pythonpath=/opt/wwc/edx-platform
"""
option_list
=
BaseCommand
.
option_list
+
(
make_option
(
'-c'
,
'--course'
,
metavar
=
'COURSE_ID'
,
dest
=
'course'
,
default
=
False
,
help
=
'Course ID for grade distribution'
),
make_option
(
'-o'
,
'--output'
,
metavar
=
'FILE'
,
dest
=
'output'
,
default
=
False
,
help
=
'Filename for grade output'
))
def
handle
(
self
,
*
args
,
**
options
):
if
os
.
path
.
exists
(
options
[
'output'
]):
raise
CommandError
(
"File {0} already exists"
.
format
(
options
[
'output'
]))
status_interval
=
100
# parse out the course into a coursekey
if
options
[
'course'
]:
course_key
=
CourseKey
.
from_string
(
options
[
'course'
])
print
"Fetching enrolled students for {0}"
.
format
(
course_key
)
enrolled_students
=
User
.
objects
.
filter
(
courseenrollment__course_id
=
course_key
)
factory
=
RequestMock
()
request
=
factory
.
get
(
'/'
)
total
=
enrolled_students
.
count
()
print
"Total enrolled: {0}"
.
format
(
total
)
course
=
courses
.
get_course_by_id
(
course_key
)
total
=
enrolled_students
.
count
()
start
=
datetime
.
datetime
.
now
()
rows
=
[]
header
=
None
print
"Fetching certificate data"
cert_grades
=
{
cert
.
user
.
username
:
cert
.
grade
for
cert
in
list
(
GeneratedCertificate
.
objects
.
filter
(
# pylint: disable=no-member
course_id
=
course_key
)
.
prefetch_related
(
'user'
)
)
}
print
"Grading students"
for
count
,
student
in
enumerate
(
enrolled_students
):
count
+=
1
if
count
%
status_interval
==
0
:
# Print a status update with an approximation of
# how much time is left based on how long the last
# interval took
diff
=
datetime
.
datetime
.
now
()
-
start
timeleft
=
diff
*
(
total
-
count
)
/
status_interval
hours
,
remainder
=
divmod
(
timeleft
.
seconds
,
3600
)
minutes
,
__
=
divmod
(
remainder
,
60
)
print
"{0}/{1} completed ~{2:02}:{3:02}m remaining"
.
format
(
count
,
total
,
hours
,
minutes
)
start
=
datetime
.
datetime
.
now
()
request
.
user
=
student
grade
=
CourseGradeFactory
()
.
create
(
student
,
course
)
if
not
header
:
header
=
[
section
[
'label'
]
for
section
in
grade
.
summary
[
u'section_breakdown'
]]
rows
.
append
([
"email"
,
"username"
,
"certificate-grade"
,
"grade"
]
+
header
)
percents
=
{
section
[
'label'
]:
section
[
'percent'
]
for
section
in
grade
.
summary
[
u'section_breakdown'
]}
row_percents
=
[
percents
[
label
]
for
label
in
header
]
if
student
.
username
in
cert_grades
:
rows
.
append
(
[
student
.
email
,
student
.
username
,
cert_grades
[
student
.
username
],
grade
.
percent
]
+
row_percents
,
)
else
:
rows
.
append
([
student
.
email
,
student
.
username
,
"N/A"
,
grade
.
percent
]
+
row_percents
)
with
open
(
options
[
'output'
],
'wb'
)
as
f
:
writer
=
csv
.
writer
(
f
)
writer
.
writerows
(
rows
)
lms/djangoapps/grades/management/commands/reset_grades.py
deleted
100644 → 0
View file @
5c88600f
"""
Reset persistent grades for learners.
"""
import
logging
from
datetime
import
datetime
from
textwrap
import
dedent
from
django.core.management.base
import
BaseCommand
,
CommandError
from
django.db.models
import
Count
from
pytz
import
utc
from
lms.djangoapps.grades.models
import
PersistentCourseGrade
,
PersistentSubsectionGrade
from
openedx.core.lib.command_utils
import
get_mutually_exclusive_required_option
,
parse_course_keys
log
=
logging
.
getLogger
(
__name__
)
DATE_FORMAT
=
"
%
Y-
%
m-
%
d
%
H:
%
M"
class
Command
(
BaseCommand
):
"""
Reset persistent grades for learners.
"""
help
=
dedent
(
__doc__
)
.
strip
()
def
add_arguments
(
self
,
parser
):
"""
Add arguments to the command parser.
"""
parser
.
add_argument
(
'--dry_run'
,
action
=
'store_true'
,
default
=
False
,
dest
=
'dry_run'
,
help
=
"Output what we're going to do, but don't actually do it. To actually delete, use --delete instead."
)
parser
.
add_argument
(
'--delete'
,
action
=
'store_true'
,
default
=
False
,
dest
=
'delete'
,
help
=
"Actually perform the deletions of the course. For a Dry Run, use --dry_run instead."
)
parser
.
add_argument
(
'--courses'
,
dest
=
'courses'
,
nargs
=
'+'
,
help
=
'Reset persistent grades for the list of courses provided.'
,
)
parser
.
add_argument
(
'--all_courses'
,
action
=
'store_true'
,
dest
=
'all_courses'
,
default
=
False
,
help
=
'Reset persistent grades for all courses.'
,
)
parser
.
add_argument
(
'--modified_start'
,
dest
=
'modified_start'
,
help
=
'Starting range for modified date (inclusive): e.g. "2016-08-23 16:43"; expected in UTC.'
,
)
parser
.
add_argument
(
'--modified_end'
,
dest
=
'modified_end'
,
help
=
'Ending range for modified date (inclusive): e.g. "2016-12-23 16:43"; expected in UTC.'
,
)
parser
.
add_argument
(
'--db_table'
,
dest
=
'db_table'
,
help
=
'Specify "subsection" to reset subsection grades or "course" to reset course grades. If absent, both '
'are reset.'
,
)
def
handle
(
self
,
*
args
,
**
options
):
course_keys
=
None
modified_start
=
None
modified_end
=
None
run_mode
=
get_mutually_exclusive_required_option
(
options
,
'delete'
,
'dry_run'
)
courses_mode
=
get_mutually_exclusive_required_option
(
options
,
'courses'
,
'all_courses'
)
db_table
=
options
.
get
(
'db_table'
)
if
db_table
not
in
{
'subsection'
,
'course'
,
None
}:
raise
CommandError
(
'Invalid value for db_table. Valid options are "subsection" or "course" only.'
)
if
options
.
get
(
'modified_start'
):
modified_start
=
utc
.
localize
(
datetime
.
strptime
(
options
[
'modified_start'
],
DATE_FORMAT
))
if
options
.
get
(
'modified_end'
):
if
not
modified_start
:
raise
CommandError
(
'Optional value for modified_end provided without a value for modified_start.'
)
modified_end
=
utc
.
localize
(
datetime
.
strptime
(
options
[
'modified_end'
],
DATE_FORMAT
))
if
courses_mode
==
'courses'
:
course_keys
=
parse_course_keys
(
options
[
'courses'
])
log
.
info
(
"reset_grade: Started in
%
s mode!"
,
run_mode
)
operation
=
self
.
_query_grades
if
run_mode
==
'dry_run'
else
self
.
_delete_grades
if
db_table
==
'subsection'
or
db_table
is
None
:
operation
(
PersistentSubsectionGrade
,
course_keys
,
modified_start
,
modified_end
)
if
db_table
==
'course'
or
db_table
is
None
:
operation
(
PersistentCourseGrade
,
course_keys
,
modified_start
,
modified_end
)
log
.
info
(
"reset_grade: Finished in
%
s mode!"
,
run_mode
)
def
_delete_grades
(
self
,
grade_model_class
,
*
args
,
**
kwargs
):
"""
Deletes the requested grades in the given model, filtered by the provided args and kwargs.
"""
grades_query_set
=
grade_model_class
.
query_grades
(
*
args
,
**
kwargs
)
num_rows_to_delete
=
grades_query_set
.
count
()
log
.
info
(
"reset_grade: Deleting
%
s:
%
d row(s)."
,
grade_model_class
.
__name__
,
num_rows_to_delete
)
grade_model_class
.
delete_grades
(
*
args
,
**
kwargs
)
log
.
info
(
"reset_grade: Deleted
%
s:
%
d row(s)."
,
grade_model_class
.
__name__
,
num_rows_to_delete
)
def
_query_grades
(
self
,
grade_model_class
,
*
args
,
**
kwargs
):
"""
Queries the requested grades in the given model, filtered by the provided args and kwargs.
"""
total_for_all_courses
=
0
grades_query_set
=
grade_model_class
.
query_grades
(
*
args
,
**
kwargs
)
grades_stats
=
grades_query_set
.
values
(
'course_id'
)
.
order_by
()
.
annotate
(
total
=
Count
(
'course_id'
))
for
stat
in
grades_stats
:
total_for_all_courses
+=
stat
[
'total'
]
log
.
info
(
"reset_grade: Would delete
%
s for COURSE
%
s:
%
d row(s)."
,
grade_model_class
.
__name__
,
stat
[
'course_id'
],
stat
[
'total'
],
)
log
.
info
(
"reset_grade: Would delete
%
s in TOTAL:
%
d row(s)."
,
grade_model_class
.
__name__
,
total_for_all_courses
,
)
lms/djangoapps/grades/management/commands/tests/test_reset_grades.py
deleted
100644 → 0
View file @
5c88600f
This diff is collapsed.
Click to expand it.
lms/djangoapps/grades/models.py
View file @
b2c6a534
...
...
@@ -39,38 +39,6 @@ BLOCK_RECORD_LIST_VERSION = 1
BlockRecord
=
namedtuple
(
'BlockRecord'
,
[
'locator'
,
'weight'
,
'raw_possible'
,
'graded'
])
class
DeleteGradesMixin
(
object
):
"""
A Mixin class that provides functionality to delete grades.
"""
@classmethod
def
query_grades
(
cls
,
course_ids
=
None
,
modified_start
=
None
,
modified_end
=
None
):
"""
Queries all the grades in the table, filtered by the provided arguments.
"""
kwargs
=
{}
if
course_ids
:
kwargs
[
'course_id__in'
]
=
[
course_id
for
course_id
in
course_ids
]
if
modified_start
:
if
modified_end
:
kwargs
[
'modified__range'
]
=
(
modified_start
,
modified_end
)
else
:
kwargs
[
'modified__gt'
]
=
modified_start
return
cls
.
objects
.
filter
(
**
kwargs
)
@classmethod
def
delete_grades
(
cls
,
*
args
,
**
kwargs
):
"""
Deletes all the grades in the table, filtered by the provided arguments.
"""
query
=
cls
.
query_grades
(
*
args
,
**
kwargs
)
query
.
delete
()
class
BlockRecordList
(
tuple
):
"""
An immutable ordered list of BlockRecord objects.
...
...
@@ -285,7 +253,7 @@ class VisibleBlocks(models.Model):
return
u"visible_blocks_cache.{}"
.
format
(
course_key
)
class
PersistentSubsectionGrade
(
DeleteGradesMixin
,
TimeStampedModel
):
class
PersistentSubsectionGrade
(
TimeStampedModel
):
"""
A django model tracking persistent grades at the subsection level.
"""
...
...
@@ -546,7 +514,7 @@ class PersistentSubsectionGrade(DeleteGradesMixin, TimeStampedModel):
)
class
PersistentCourseGrade
(
DeleteGradesMixin
,
TimeStampedModel
):
class
PersistentCourseGrade
(
TimeStampedModel
):
"""
A django model tracking persistent course grades.
"""
...
...
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