diff --git a/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py b/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py new file mode 100644 index 0000000000000000000000000000000000000000..2bcef73f76a610f3cfa994a55d1000e3b9af29fb --- /dev/null +++ b/common/djangoapps/student/migrations/0036_userpasswordtogglehistory.py @@ -0,0 +1,33 @@ +# Generated by Django 2.2.16 on 2020-10-15 10:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('student', '0035_bulkchangeenrollmentconfiguration'), + ] + + operations = [ + migrations.CreateModel( + name='UserPasswordToggleHistory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), + ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), + ('comment', models.CharField(blank=True, help_text='Add a reason', max_length=255, null=True)), + ('disabled', models.BooleanField(default=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='password_toggle_history', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created'], + }, + ), + ] diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 0938b181b9aec0abc7cb35baaf52fcf70967aa45..b55d0afa61379e5ec95c6ed46b2f52370d9e9a03 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -3103,3 +3103,19 @@ class CourseEnrollmentCelebration(TimeStampedModel): return enrollment.celebration.celebrate_first_section except CourseEnrollmentCelebration.DoesNotExist: return False + + +class UserPasswordToggleHistory(TimeStampedModel): + """ + Keeps track of user password disable/enable history + """ + user = models.ForeignKey(User, related_name='password_toggle_history', on_delete=models.CASCADE) + comment = models.CharField(max_length=255, help_text=_("Add a reason"), blank=True, null=True) + disabled = models.BooleanField(default=True) + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + + class Meta: + ordering = ['-created'] + + def __str__(self): + return self.comment diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index bdd9bd64d973b22d4b430688d85d4be7774d535c..c0b00543fd15c2f09cf9366a39967019e21c999f 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -108,7 +108,8 @@ class SupportViewManageUserTests(SupportViewTestCase): ) url = reverse('support:manage_user_detail') + test_user.username response = self.client.post(url, data={ - 'username_or_email': test_user.username + 'username_or_email': test_user.username, + 'comment': 'Test comment' }) data = json.loads(response.content.decode('utf-8')) self.assertEqual(data['success_msg'], 'User Disabled Successfully') diff --git a/lms/djangoapps/support/views/manage_user.py b/lms/djangoapps/support/views/manage_user.py index 7642ca734e91e4be833089686887ac50c4e445fe..ecf618e62eddfbc25e9d021c8e36c7dfcc0be4b6 100644 --- a/lms/djangoapps/support/views/manage_user.py +++ b/lms/djangoapps/support/views/manage_user.py @@ -11,6 +11,7 @@ from django.utils.translation import ugettext as _ from django.views.generic import View from rest_framework.generics import GenericAPIView +from student.models import UserPasswordToggleHistory from edxmako.shortcuts import render_to_response from lms.djangoapps.support.decorators import require_support_permission from openedx.core.djangoapps.user_api.accounts.serializers import AccountUserSerializer @@ -66,10 +67,17 @@ class ManageUserDetailView(GenericAPIView): user = get_user_model().objects.get( Q(username=username_or_email) | Q(email=username_or_email) ) + comment = request.data.get("comment") if user.has_usable_password(): user.set_unusable_password() + UserPasswordToggleHistory.objects.create( + user=user, comment=comment, created_by=request.user, disabled=True + ) else: user.set_password(generate_password(length=25)) + UserPasswordToggleHistory.objects.create( + user=user, comment=comment, created_by=request.user, disabled=False + ) user.save() if user.has_usable_password(): diff --git a/openedx/core/djangoapps/user_api/accounts/serializers.py b/openedx/core/djangoapps/user_api/accounts/serializers.py index 1110bfad9c868ae9c170f871cd41e6fadadc38b2..bdf7cd8aeb1e190928560043639d31ae69235992 100644 --- a/openedx/core/djangoapps/user_api/accounts/serializers.py +++ b/openedx/core/djangoapps/user_api/accounts/serializers.py @@ -14,6 +14,7 @@ from django.urls import reverse from rest_framework import serializers from six import text_type +from student.models import UserPasswordToggleHistory from lms.djangoapps.badges.utils import badges_enabled from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.user_api import errors @@ -216,14 +217,30 @@ class UserReadOnlySerializer(serializers.Serializer): return visible_serialized_account +class UserAccountDisableHistorySerializer(serializers.ModelSerializer): + """ + Class that serializes User account disable history + """ + created_by = serializers.SerializerMethodField() + + class Meta(object): + model = UserPasswordToggleHistory + fields = ("created", "comment", "disabled", "created_by") + + def get_created_by(self, user_password_toggle_history): + return user_password_toggle_history.created_by.username + + class AccountUserSerializer(serializers.HyperlinkedModelSerializer, ReadOnlyFieldsSerializerMixin): """ Class that serializes the portion of User model needed for account information. """ + password_toggle_history = UserAccountDisableHistorySerializer(many=True, required=False) + class Meta(object): model = User - fields = ("username", "email", "date_joined", "is_active") - read_only_fields = ("username", "email", "date_joined", "is_active") + fields = ("username", "email", "date_joined", "is_active", "password_toggle_history") + read_only_fields = fields explicit_read_only_fields = ()