diff --git a/common/djangoapps/student/management/commands/create_user.py b/common/djangoapps/student/management/commands/create_user.py index bf4c084dca..d375dedc54 100644 --- a/common/djangoapps/student/management/commands/create_user.py +++ b/common/djangoapps/student/management/commands/create_user.py @@ -4,8 +4,10 @@ from student.models import CourseEnrollment, Registration from student.views import _do_create_account from django.contrib.auth.models import User +from track.management.tracked_command import TrackedCommand -class Command(BaseCommand): + +class Command(TrackedCommand): help = """ This command creates and registers a user in a given course as "audit", "verified" or "honor". diff --git a/common/djangoapps/track/management/__init__.py b/common/djangoapps/track/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/track/management/tests/test_tracked_command.py b/common/djangoapps/track/management/tests/test_tracked_command.py new file mode 100644 index 0000000000..695703a192 --- /dev/null +++ b/common/djangoapps/track/management/tests/test_tracked_command.py @@ -0,0 +1,29 @@ +import json +from StringIO import StringIO +from django.test import TestCase + +from eventtracking import tracker as eventtracker + +from track.management.tracked_command import TrackedCommand + + +class DummyCommand(TrackedCommand): + """A locally-defined command, for testing, that returns the current context as a JSON string.""" + def handle(self, *args, **options): + return json.dumps(eventtracker.get_tracker().resolve_context()) + + +class CommandsTestBase(TestCase): + + def _run_dummy_command(self, *args, **kwargs): + """Runs the test command's execute method directly, and outputs a dict of the current context.""" + out = StringIO() + DummyCommand().execute(*args, stdout=out, **kwargs) + out.seek(0) + return json.loads(out.read()) + + def test_command(self): + args = ['whee'] + kwargs = {'key1': 'default', 'key2': True} + json_out = self._run_dummy_command(*args, **kwargs) + self.assertEquals(json_out['command'], 'unknown') diff --git a/common/djangoapps/track/management/tracked_command.py b/common/djangoapps/track/management/tracked_command.py new file mode 100644 index 0000000000..8c22cd4093 --- /dev/null +++ b/common/djangoapps/track/management/tracked_command.py @@ -0,0 +1,59 @@ +"""Provides management command calling info to tracking context.""" + +from django.core.management.base import BaseCommand + +from eventtracking import tracker + + +class TrackedCommand(BaseCommand): + """ + Provides management command calling info to tracking context. + + Information provided to context includes the following value: + + 'command': the program name and the subcommand used to run a management command. + + In future, other values (such as args and options) could be added as needed. + + An example tracking log entry resulting from running the 'create_user' management command: + + { + "username": "anonymous", + "host": "", + "event_source": "server", + "event_type": "edx.course.enrollment.activated", + "context": { + "course_id": "edX/Open_DemoX/edx_demo_course", + "org_id": "edX", + "command": "./manage.py create_user", + }, + "time": "2014-01-06T15:59:49.599522+00:00", + "ip": "", + "event": { + "course_id": "edX/Open_DemoX/edx_demo_course", + "user_id": 29, + "mode": "verified" + }, + "agent": "", + "page": null + } + + The name of the context used to add (and remove) these values is "edx.mgmt.command". + The context name is used to allow the context additions to be scoped, but doesn't + appear in the context itself. + """ + prog_name = 'unknown' + + def create_parser(self, prog_name, subcommand): + """Wraps create_parser to snag command line info.""" + self.prog_name = "{} {}".format(prog_name, subcommand) + return super(TrackedCommand, self).create_parser(prog_name, subcommand) + + def execute(self, *args, **options): + """Wraps base execute() to add command line to tracking context.""" + context = { + 'command': self.prog_name, + } + COMMAND_CONTEXT_NAME = 'edx.mgmt.command' + with tracker.get_tracker().context(COMMAND_CONTEXT_NAME, context): + super(TrackedCommand, self).execute(*args, **options)