diff --git a/courses/admin.py b/courses/admin.py index 920747be20..954245300c 100644 --- a/courses/admin.py +++ b/courses/admin.py @@ -200,6 +200,7 @@ class CourseRunEnrollmentAuditInline(admin.TabularInline): "enrollment_id", "created_on", "acting_user", + "call_stack", "data_before", "data_after", ] diff --git a/courses/migrations/0045_audit_modified_by.py b/courses/migrations/0045_audit_modified_by.py new file mode 100644 index 0000000000..8354a9228b --- /dev/null +++ b/courses/migrations/0045_audit_modified_by.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.23 on 2024-02-29 18:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("courses", "0044_alter_courserun_live"), + ] + + operations = [ + migrations.AddField( + model_name="courserunenrollmentaudit", + name="modified_by", + field=models.CharField(blank=True, default="", max_length=750, null=True), + ), + migrations.AddField( + model_name="courserungradeaudit", + name="modified_by", + field=models.CharField(blank=True, default="", max_length=750, null=True), + ), + migrations.AddField( + model_name="programenrollmentaudit", + name="modified_by", + field=models.CharField(blank=True, default="", max_length=750, null=True), + ), + ] diff --git a/courses/migrations/0046_audit_rename_call_stack.py b/courses/migrations/0046_audit_rename_call_stack.py new file mode 100644 index 0000000000..3d50dec2e7 --- /dev/null +++ b/courses/migrations/0046_audit_rename_call_stack.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.23 on 2024-03-06 13:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("courses", "0045_audit_modified_by"), + ] + + operations = [ + migrations.RenameField( + model_name="courserunenrollmentaudit", + old_name="modified_by", + new_name="call_stack", + ), + migrations.RenameField( + model_name="courserungradeaudit", + old_name="modified_by", + new_name="call_stack", + ), + migrations.RenameField( + model_name="programenrollmentaudit", + old_name="modified_by", + new_name="call_stack", + ), + ] diff --git a/ecommerce/management/commands/refund_fulfilled_order.py b/ecommerce/management/commands/refund_fulfilled_order.py index e5b4e01865..4cf0dfe098 100644 --- a/ecommerce/management/commands/refund_fulfilled_order.py +++ b/ecommerce/management/commands/refund_fulfilled_order.py @@ -1,6 +1,6 @@ """ Looks up a fulfilled order in the system, sets it to Refunded, and then adjusts -the enrollments accordingly. +the enrollments accordingly. - If --unenroll is specified, the learner will be unenrolled from the course run associated with the order. @@ -9,8 +9,8 @@ This does not make any sort of call to CyberSource or any other payment gateway to perform a refund - you're expected to have refunded the learner's money -manually already. (At time of writing, PayPal transactions can't be refunded -using the normal means, so they get refunded manually via CyberSource and then +manually already. (At time of writing, PayPal transactions can't be refunded +using the normal means, so they get refunded manually via CyberSource and then this command comes in to clean up afterwards.) """ @@ -83,8 +83,7 @@ def handle(self, *args, **kwargs): f"Changing enrollment for {order.purchaser.username} in {enrollment.run} to 'audit'" ) - enrollment.enrollment_mode = "audit" - enrollment.save() + enrollment.update_mode_and_save("audit") enroll_in_edx_course_runs( order.purchaser, [enrollment.run], mode="audit" diff --git a/main/models.py b/main/models.py index 7c99004c8f..b906ca02ee 100644 --- a/main/models.py +++ b/main/models.py @@ -1,10 +1,13 @@ """ Common model classes """ +import traceback + from django.conf import settings from django.db import transaction from django.db.models import ( CASCADE, + CharField, DateTimeField, ForeignKey, JSONField, @@ -18,6 +21,7 @@ class AuditModel(TimestampedModel): """An abstract base class for audit models""" acting_user = ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=CASCADE) + call_stack = CharField(default="", max_length=750, null=True, blank=True) data_before = JSONField(blank=True, null=True) data_after = JSONField(blank=True, null=True) @@ -84,8 +88,13 @@ def save_and_log(self, acting_user, *args, **kwargs): if before_obj is not None: before_dict = before_obj.to_dict() + call_stack = "".join(traceback.format_stack()[-6:-2]) + audit_kwargs = dict( - acting_user=acting_user, data_before=before_dict, data_after=self.to_dict() + acting_user=acting_user, + call_stack=call_stack, + data_before=before_dict, + data_after=self.to_dict(), ) audit_class = self.get_audit_class() audit_kwargs[audit_class.get_related_field_name()] = self diff --git a/main/utils_test.py b/main/utils_test.py index c527594edc..16f29776f3 100644 --- a/main/utils_test.py +++ b/main/utils_test.py @@ -25,6 +25,7 @@ def test_get_field_names(): "acting_user", "created_on", "updated_on", + "call_stack", }