diff --git a/common/djangoapps/student/management/commands/pearson_export_cdd.py b/common/djangoapps/student/management/commands/pearson_export_cdd.py index b10e92d92d..fec2ec9297 100644 --- a/common/djangoapps/student/management/commands/pearson_export_cdd.py +++ b/common/djangoapps/student/management/commands/pearson_export_cdd.py @@ -1,14 +1,17 @@ import csv import uuid -from collections import defaultdict, OrderedDict +from collections import OrderedDict from datetime import datetime -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand from student.models import TestCenterUser +from os.path import isdir +from fs.path import pathjoin class Command(BaseCommand): CSV_TO_MODEL_FIELDS = OrderedDict([ + # Skipping optional field CandidateID ("ClientCandidateID", "client_candidate_id"), ("FirstName", "first_name"), ("LastName", "last_name"), @@ -44,9 +47,36 @@ class Command(BaseCommand): print Command.help return - self.reset_sample_data() + # use options to set these: + dump_all = False + # self.reset_sample_data() - with open(args[0], "wb") as outfile: + # update time should use UTC in order to be comparable to the user_updated_at + # field + uploaded_at = datetime.utcnow() + + # if specified destination is an existing directory, then + # create a filename for it automatically. If it doesn't exist, + # or exists as a file, then we will just write to it. + # Name will use timestamp -- this is UTC, so it will look funny, + # but it should at least be consistent with the other timestamps + # used in the system. + dest = args[0] + if isdir(dest): + destfile = pathjoin(dest, uploaded_at.strftime("cdd-%Y%m%d-%H%M%S.dat")) + else: + destfile = dest + + # strings must be in latin-1 format. CSV parser will + # otherwise convert unicode objects to ascii. + def ensure_encoding(value): + if isinstance(value, unicode): + return value.encode('iso-8859-1'); + else: + return value; + + + with open(destfile, "wb") as outfile: writer = csv.DictWriter(outfile, Command.CSV_TO_MODEL_FIELDS, delimiter="\t", @@ -54,11 +84,15 @@ class Command(BaseCommand): extrasaction='ignore') writer.writeheader() for tcu in TestCenterUser.objects.order_by('id'): - record = dict((csv_field, getattr(tcu, model_field)) - for csv_field, model_field - in Command.CSV_TO_MODEL_FIELDS.items()) - record["LastUpdate"] = record["LastUpdate"].strftime("%Y/%m/%d %H:%M:%S") - writer.writerow(record) + if dump_all or tcu.needs_uploading: + record = dict((csv_field, ensure_encoding(getattr(tcu, model_field))) + for csv_field, model_field + in Command.CSV_TO_MODEL_FIELDS.items()) + record["LastUpdate"] = record["LastUpdate"].strftime("%Y/%m/%d %H:%M:%S") + writer.writerow(record) + tcu.uploaded_at = uploaded_at + tcu.save() + def reset_sample_data(self): def make_sample(**kwargs): diff --git a/common/djangoapps/student/management/commands/pearson_make_tc_user.py b/common/djangoapps/student/management/commands/pearson_make_tc_user.py index d974c25b6b..97e30002c0 100644 --- a/common/djangoapps/student/management/commands/pearson_make_tc_user.py +++ b/common/djangoapps/student/management/commands/pearson_make_tc_user.py @@ -1,20 +1,13 @@ -import uuid -from datetime import datetime from optparse import make_option from django.contrib.auth.models import User -from django.core.management.base import BaseCommand, CommandError +from django.core.management.base import BaseCommand -from student.models import TestCenterUser +from student.models import TestCenterUser, TestCenterUserForm class Command(BaseCommand): option_list = BaseCommand.option_list + ( - make_option( - '--client_candidate_id', - action='store', - dest='client_candidate_id', - help='ID we assign a user to identify them to Pearson' - ), + # demographics: make_option( '--first_name', action='store', @@ -64,6 +57,25 @@ class Command(BaseCommand): dest='phone_country_code', help='Phone country code, just "1" for the USA' ), + # internal values: + make_option( + '--client_candidate_id', + action='store', + dest='client_candidate_id', + help='ID we assign a user to identify them to Pearson' + ), + make_option( + '--upload_status', + action='store', + dest='upload_status', + help='status value assigned by Pearson' + ), + make_option( + '--upload_error_message', + action='store', + dest='upload_error_message', + help='error message provided by Pearson on a failure.' + ), ) args = "" help = "Create a TestCenterUser entry for a given Student" @@ -79,7 +91,41 @@ class Command(BaseCommand): print username our_options = dict((k, v) for k, v in options.items() - if Command.is_valid_option(k)) + if Command.is_valid_option(k) and v is not None) student = User.objects.get(username=username) - student.test_center_user = TestCenterUser(**our_options) - student.test_center_user.save() + try: + testcenter_user = TestCenterUser.objects.get(user=student) + needs_updating = testcenter_user.needs_update(our_options) + except TestCenterUser.DoesNotExist: + # do additional initialization here: + testcenter_user = TestCenterUser.create(student) + needs_updating = True + + if needs_updating: + form = TestCenterUserForm(instance=testcenter_user, data=our_options) + if form.is_valid(): + form.update_and_save() + else: + if (len(form.errors) > 0): + print "Field Form errors encountered:" + for fielderror in form.errors: + print "Field Form Error: %s" % fielderror + if (len(form.non_field_errors()) > 0): + print "Non-field Form errors encountered:" + for nonfielderror in form.non_field_errors: + print "Non-field Form Error: %s" % nonfielderror + + else: + print "No changes necessary to make to existing user's demographics." + + # override internal values: + change_internal = False + testcenter_user = TestCenterUser.objects.get(user=student) + for internal_field in [ 'upload_error_message', 'upload_status', 'client_candidate_id']: + if internal_field in our_options: + testcenter_user = our_options[internal_field] + change_internal = True + + if change_internal: + testcenter_user.save() + diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 95ceb34528..d5058ae92f 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -208,6 +208,9 @@ class TestCenterUser(models.Model): candidate_id = models.IntegerField(null=True, db_index=True) confirmed_at = models.DateTimeField(null=True, db_index=True) + @property + def needs_uploading(self): + return self.uploaded_at is None or self.uploaded_at < self.user_updated_at @staticmethod def user_provided_fields(): @@ -223,7 +226,7 @@ class TestCenterUser(models.Model): # needs_updating = any([__getattribute__(fieldname) != dict[fieldname] # for fieldname in TestCenterUser.user_provided_fields()]) for fieldname in TestCenterUser.user_provided_fields(): - if self.__getattribute__(fieldname) != dict[fieldname]: + if fieldname in dict and self.__getattribute__(fieldname) != dict[fieldname]: return True return False @@ -231,7 +234,7 @@ class TestCenterUser(models.Model): @staticmethod def _generate_candidate_id(): NUM_DIGITS = 12 - return u"edX%0d" % randint(1, 10**NUM_DIGITS-1) # binascii.hexlify(os.urandom(8)) + return u"edX%0d" % randint(1, 10**NUM_DIGITS-1) @staticmethod def create(user): @@ -266,7 +269,7 @@ class TestCenterUserForm(ModelForm): def update_and_save(self): new_user = self.save(commit=False) # create additional values here: - new_user.user_updated_at = datetime.now() + new_user.user_updated_at = datetime.utcnow() new_user.save() # add validation: @@ -505,7 +508,7 @@ class TestCenterRegistrationForm(ModelForm): def update_and_save(self): registration = self.save(commit=False) # create additional values here: - registration.user_updated_at = datetime.now() + registration.user_updated_at = datetime.utcnow() registration.save() # TODO: add validation code for values added to accommodation_code field.