diff --git a/course/staff_views.py b/course/staff_views.py index a7296f160..8511e084e 100644 --- a/course/staff_views.py +++ b/course/staff_views.py @@ -28,7 +28,7 @@ def format_group(group: StudentGroup, pseudonymized: bool): if pseudonymized: for member in group.members.all(): - format_user(member.user, True) + format_user(member.user, True, member) return group @@ -71,7 +71,7 @@ def _get_students_with_tags(self) -> List[Dict[str, Any]]: participants = ci.all_students.prefetch_tags(ci) data = [] for participant in participants: - format_user(participant.user, self.pseudonymize) + format_user(participant.user, self.pseudonymize, participant) user_id = participant.user.id user_tags = CachedStudent(ci, participant.user).data user_tags_html = ' '.join(tags[slug].html_label for slug in user_tags['tag_slugs'] if slug in tags) @@ -124,7 +124,7 @@ def get_resource_objects(self): ) else: group = StudentGroup(course_instance=self.instance) - self.group = format_group(group, self.pseudonymize) + self.group = group def get_form_kwargs(self): kwargs = super().get_form_kwargs() diff --git a/course/templates/course/_course_menu.html b/course/templates/course/_course_menu.html index 2e11118f7..6857390b3 100644 --- a/course/templates/course/_course_menu.html +++ b/course/templates/course/_course_menu.html @@ -202,6 +202,19 @@

{% translate "COURSE_STAFF" %}

{% translate "SUBMISSION_DEVIATIONS" %} + + + {% endif %} {% for group in course_menu.staff_link_groups %} diff --git a/deviations/viewbase.py b/deviations/viewbase.py index daddb2351..d7d608499 100644 --- a/deviations/viewbase.py +++ b/deviations/viewbase.py @@ -284,7 +284,7 @@ def get_deviation_groups( lambda obj: (obj.submitter, obj.exercise.course_module), ) for (submitter, module), deviations_iter in deviation_groups: - format_user(submitter.user, pseudonymize) + format_user(submitter.user, pseudonymize, submitter) deviations = list(deviations_iter) can_group = True show_granter = True diff --git a/exercise/staff_views.py b/exercise/staff_views.py index 62591798e..f403406c9 100644 --- a/exercise/staff_views.py +++ b/exercise/staff_views.py @@ -52,7 +52,8 @@ def format_submission(submission: Submission, pseudonymized: bool): if pseudonymized: for submitter in submission.submitters.all(): - submitter.user = format_user(submitter.user, pseudonymized=True) + pseudo = True + format_user(submitter.user, pseudo, submitter) return submission @@ -76,6 +77,7 @@ def get_common_objects(self) -> None: ) for submission in qs: format_submission(submission, self.pseudonymize) + print(submission.submitters) self.all = self.request.GET.get('all', None) self.all_url = self.exercise.get_submission_list_url() + "?all=yes" self.submissions = qs if self.all else qs[:self.default_limit] @@ -127,7 +129,8 @@ def get_common_objects(self) -> None: ) if self.pseudonymize: for profile in profiles.values(): - format_user(profile.user, pseudonymized=True) + pseudo = True + format_user(profile.user, pseudo, profile) # Add UserProfile instances to the dicts in submitter_summaries, so we can # use the 'profiles' template tag. for submitter_summary in submitter_summaries: @@ -184,7 +187,7 @@ def get_common_objects(self) -> None: self.not_best = False self.not_last = False for submission in self.submissions: - format_submission(submission, self.pseudonymize) + format_submission(self.submission, self.pseudonymize) if submission.id != self.submission.id: if submission.force_exercise_points: self.not_final = True @@ -407,8 +410,9 @@ def get_common_objects(self): super().get_common_objects() self.tags = [USERTAG_INTERNAL, USERTAG_EXTERNAL] self.tags.extend(self.instance.usertags.all()) + self.pseudonymize = self.request.session.get("pseudonymize", False) self.note( - 'tags', + 'tags', 'pseudonymize', ) @@ -421,8 +425,9 @@ def get_common_objects(self): self.tags = list(self.instance.usertags.all()) self.internal_user_label = settings_text('INTERNAL_USER_LABEL') self.external_user_label = settings_text('EXTERNAL_USER_LABEL') + self.pseudonymize = self.request.session.get("pseudonymize", False) self.note( - 'tags', 'internal_user_label', 'external_user_label', + 'tags', 'internal_user_label', 'external_user_label', 'pseudonymize', ) @@ -442,7 +447,7 @@ def get_resource_objects(self): User, id=self.kwargs[self.user_kw], ) - self.student = format_user(student, self.pseudonymize) + self.student = format_user(student, self.pseudonymize, student.userprofile) self.note('student') def get_common_objects(self): diff --git a/exercise/templates/exercise/staff/analytics.html b/exercise/templates/exercise/staff/analytics.html index 79305e663..bfc70f48f 100644 --- a/exercise/templates/exercise/staff/analytics.html +++ b/exercise/templates/exercise/staff/analytics.html @@ -113,35 +113,41 @@ - + +{% else %} +
+ This view is not available when pseudonymization is enabled. Disable it to see visualizations. +
+{% endif %} {% endblock %} diff --git a/exercise/templates/exercise/staff/results.html b/exercise/templates/exercise/staff/results.html index 66b6f8025..e85236d9b 100644 --- a/exercise/templates/exercise/staff/results.html +++ b/exercise/templates/exercise/staff/results.html @@ -43,14 +43,20 @@ results_staff.js is the main script for this page. --> - +{% if not pseudonymize %} + +{% else %} +
+ This view is not available when pseudonymization is enabled. Disable it to see all results. +
+{% endif %} diff --git a/exercise/viewbase.py b/exercise/viewbase.py index 9431d1e30..c98ef3dce 100644 --- a/exercise/viewbase.py +++ b/exercise/viewbase.py @@ -256,7 +256,7 @@ def get_summary_user(self) -> Optional[User]: @cached_property def submission_entry(self) -> int: - return next(s for s in self.submissions if s.id == self.submission.id) + return next(s for s in self.submissions if s.id == self.submission.id) @cached_property def index(self) -> int: diff --git a/external_services/templatetags/external_services.py b/external_services/templatetags/external_services.py index 3b13df5c0..06dcaeee0 100644 --- a/external_services/templatetags/external_services.py +++ b/external_services/templatetags/external_services.py @@ -1,4 +1,5 @@ import string + from django import template from lib.errors import TagUsageError diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index d1531d404..e1cf94e4c 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -1017,6 +1017,14 @@ msgstr "Deadline deviations" msgid "SUBMISSION_DEVIATIONS" msgstr "Submission deviations" +#: course/templates/course/_course_menu.html +msgid "UNPSEUDONYMIZE" +msgstr "Un-pseudonymize" + +#: course/templates/course/_course_menu.html +msgid "PSEUDONYMIZE" +msgstr "Pseudonymize" + #: course/templates/course/_enroll_form.html msgid "ENROLL_THROUGH_SIS" msgstr "" diff --git a/locale/fi/LC_MESSAGES/django.po b/locale/fi/LC_MESSAGES/django.po index 614d01ebb..f26d3d95c 100644 --- a/locale/fi/LC_MESSAGES/django.po +++ b/locale/fi/LC_MESSAGES/django.po @@ -1023,6 +1023,15 @@ msgstr "Määräaikojen poikkeamat" msgid "SUBMISSION_DEVIATIONS" msgstr "Palautuskertojen poikkeamat" +#: course/templates/course/_course_menu.html +msgid "UNPSEUDONYMIZE" +msgstr "Pseudonymisointi pois päältä" + +#: course/templates/course/_course_menu.html +msgid "PSEUDONYMIZE" +msgstr "Pseudonymisointi päälle" + + #: course/templates/course/_enroll_form.html msgid "ENROLL_THROUGH_SIS" msgstr "" diff --git a/templates/base.html b/templates/base.html index 40ed868a0..061d8c164 100644 --- a/templates/base.html +++ b/templates/base.html @@ -255,9 +255,6 @@

{% translate "SITE" %}

  • {% translate "ACCESSIBILITY_STATEMENT" %}
  • {% translate "SUPPORT" %}
  • {% translate "FEEDBACK" %}
  • - {% if is_course_staff %} -
  • {% if pseudonymize %} Unpseudonymize {% else %} Pseudonymize {% endif %} personal data
  • - {% endif %}
  • diff --git a/userprofile/pseudonymize.py b/userprofile/pseudonymize.py index f5508b3b9..54dca3995 100644 --- a/userprofile/pseudonymize.py +++ b/userprofile/pseudonymize.py @@ -1,12 +1,11 @@ from faker import Faker import hashlib -import random from django.contrib.auth.models import User from userprofile.models import UserProfile -fake = Faker() +fake = Faker(['fi_FI', 'en_US'], use_weighting=False) num_fakes = 500 fakes = { @@ -16,19 +15,19 @@ 'username': [fake.unique.user_name() for _ in range(num_fakes)], } + def pseudonymize(key: str, data: str): - hashkey = int(hashlib.md5(data.encode('utf-8')).hexdigest(), 16) % num_fakes + hashkey = int(hashlib.sha256(data.encode('utf-8')).hexdigest(), 16) % num_fakes if key in fakes: return fakes[key][hashkey] return key -def format_user(user: User, pseudonymized: bool): + +def format_user(user: User, pseudonymized: bool, user_profile: UserProfile = None): if pseudonymized: - for _user_profile in UserProfile.objects.all(): - if _user_profile.user == user: - _user_profile.student_id = random.randint(10000,90000) # Return formatted versions of the user's attributes and all the user class's methods - user.student_id = 99999 + if user_profile is not None: + user_profile.student_id = str(fake.unique.random_int(min=10, max=10000)) user.first_name = pseudonymize('first_name', user.first_name) user.last_name = pseudonymize('last_name', user.last_name) user.email = pseudonymize('email', user.email)