From e7dfe131e2a255aa5d53bb69462e8af07e9e4a6e Mon Sep 17 00:00:00 2001 From: Stefan Kairinos <118008817+SKairinos@users.noreply.github.com> Date: Tue, 14 Nov 2023 14:32:34 +0000 Subject: [PATCH] fix: track worksheet badges (#1829) * fix: track worksheet usage * fix: track worksheet badges * make user nullable * remove unused args * Merge branch 'worksheet_usage' into worksheet_badges * remove unused args * silence pylint unused arg error * silence pylint unused arg error * fix: worksheet usage * merge * simplify * relock * Merge branch 'worksheet_usage' into worksheet_badges * merge from master * testy signals * pytest config --- .gitignore | 4 +-- .vscode/launch.json | 29 ++++++++++++++++ .vscode/settings.json | 7 ++++ Pipfile | 2 +- aimmo/migrations/0036_worksheetbadge.py | 30 +++++++++++++++++ aimmo/models.py | 39 ++++++++++++++++++++- aimmo/tests/test_signals.py | 45 +++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 aimmo/migrations/0036_worksheetbadge.py create mode 100644 aimmo/tests/test_signals.py diff --git a/.gitignore b/.gitignore index b1d9d42ba..8f633861e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ __pycache__/ *.so # Editors & workspaces -.vscode/ -**.vscode/ +# .vscode/ +# **.vscode/ *.code-workspace # Distribution / packaging diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..3b15f64cf --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Django Server", + "type": "python", + "request": "launch", + "django": true, + "justMyCode": false, + "program": "${workspaceFolder}/example_project/manage.py", + "args": [ + "runserver", + "localhost:8000" + ] + }, + { + "name": "Pytest", + "type": "python", + "request": "test", + "justMyCode": false, + "presentation": { + "hidden": true + } + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e63437a69 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": [ + "./aimmo" + ] +} \ No newline at end of file diff --git a/Pipfile b/Pipfile index 9e20a494a..74759258c 100644 --- a/Pipfile +++ b/Pipfile @@ -8,7 +8,7 @@ black = "*" codeforlife-portal = "*" django-import-export = "==3.3.1" django-test-migrations = "==1.2.0" -pytest = "~=6.2" +pytest = "*" pytest-cov = "*" pytest-django = "==4.5.2" pytest-pythonpath = "*" diff --git a/aimmo/migrations/0036_worksheetbadge.py b/aimmo/migrations/0036_worksheetbadge.py new file mode 100644 index 000000000..6acb6bcfe --- /dev/null +++ b/aimmo/migrations/0036_worksheetbadge.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.23 on 2023-11-10 11:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('aimmo', '0035_worksheetusage'), + ] + + operations = [ + migrations.CreateModel( + name='WorksheetBadge', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('worksheet_id', models.IntegerField()), + ('badge_id', models.IntegerField()), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'unique_together': {('user', 'worksheet_id', 'badge_id')}, + }, + ), + ] diff --git a/aimmo/models.py b/aimmo/models.py index 6e79add11..170625d62 100755 --- a/aimmo/models.py +++ b/aimmo/models.py @@ -1,8 +1,9 @@ import json +import re import secrets import typing as t -from common.models import Class, Teacher +from common.models import Class, Teacher, UserProfile from django.contrib.auth.models import User from django.db import models from django.dispatch import receiver @@ -205,6 +206,42 @@ class WorksheetUsage(models.Model): created_at = models.DateTimeField(default=timezone.now) +class WorksheetBadge(models.Model): + user = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + worksheet_id = models.IntegerField() + badge_id = models.IntegerField() + created_at = models.DateTimeField(default=timezone.now) + + class Meta: + unique_together = ("user", "worksheet_id", "badge_id") + + +@receiver(models.signals.pre_save, sender=UserProfile) +def create_worksheet_badge(instance: UserProfile, **_kwargs): + aimmo_badges: t.Optional[str] = instance.aimmo_badges + if aimmo_badges is None: + return + + if instance.pk is not None: + old_instance: UserProfile = UserProfile.objects.get(pk=instance.pk) + if old_instance.aimmo_badges == instance.aimmo_badges: + return + + for badge in aimmo_badges.split(","): + match = re.match(r"(\d+):(\d+)", badge) + if not match: + continue + + worksheet_badge = { + "user": instance.user, + "worksheet_id": match.group(1), + "badge_id": match.group(2), + } + + if not WorksheetBadge.objects.filter(**worksheet_badge).exists(): + WorksheetBadge.objects.create(**worksheet_badge) + + # TODO: Replace with a ModelSerializer class GameSerializer(serializers.Serializer): name = serializers.CharField(max_length=100, required=False) diff --git a/aimmo/tests/test_signals.py b/aimmo/tests/test_signals.py new file mode 100644 index 000000000..a2da5082e --- /dev/null +++ b/aimmo/tests/test_signals.py @@ -0,0 +1,45 @@ +from common.models import UserProfile +from django.contrib.auth.models import User +from django.test import TestCase + +from ..models import WorksheetBadge + + +class TestSignals(TestCase): + def test_pre_save___user_profile__create_worksheet_badge(self): + user = User.objects.create_user( + "JDoe", + "john.doe@codeforlife.com", + "password", + ) + + user_profile = UserProfile.objects.create(user=user) + assert not WorksheetBadge.objects.exists() + + def assert_worksheet_badges(): + worksheet_badges = [ + WorksheetBadge( + user=user, + worksheet_id=1, + badge_id=1, + ), + WorksheetBadge( + user=user, + worksheet_id=1, + badge_id=2, + ), + ] + assert len(worksheet_badges) == WorksheetBadge.objects.count() + for worksheet_badge, expected_worksheet_badge in zip(WorksheetBadge.objects.all(), worksheet_badges): + assert ( + worksheet_badge.user == expected_worksheet_badge.user + and worksheet_badge.worksheet_id == expected_worksheet_badge.worksheet_id + and worksheet_badge.badge_id == expected_worksheet_badge.badge_id + ) + + user_profile.aimmo_badges = "1:1,1:2" + user_profile.save() + assert_worksheet_badges() + + user_profile.save() + assert_worksheet_badges()