diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e945cc37..a9ae6891f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,52 @@ # Changelog +## 2.0.0 + +Adds gamification elements (points and achievements) to the website, including for all previous submissions for each user. + +- Add gamification system that allows users to earn points and achievements. +- Add ability for the CodeWOF server to recalculate a user's points and achievements from their past attempts. +- Improve the CodeWOF admin site with more information and filtering options. +- Update Django from 2.1.5 to 2.2.3. +- Dependency updates: + - Remove django-coverage-plugin. + - Update coverage from 4.5.4 to 5.1. + - Update django-activeurl from 0.1.12 to 0.2.0. + - Update django-allauth from 0.39.1 to 0.41.0. + - Update django-ckeditor from 5.7.1 to 5.9.0. + - Update django-crispy-forms from 1.7.2 to 1.9.0. + - Update django-debug-toolbar from 2.0 to 2.2. + - Update django-extensions from 2.2.1 to 2.2.9. + - Update django-modeltranslation from 0.13.3 to 0.14.4. + - Update django-model-utils from 3.2.0 to 4.0.0. + - Update django-recaptcha from 2.0.5 to 2.0.6. + - Update django-redis from 4.10.0 to 4.11.0. + - Update django-storages from 1.7.1 to 1.9.1. + - Update google-api-python-client from 1.7.11 to 1.7.12. + - Update google-auth from 1.6.3 to 1.12.0. + - Update google-cloud-logging from 1.12.1 to 1.15.0. + - Update mypy from 0.720 to 0.770. + - Update Pillow from 6.1.0 to 7.0.0. + - Update pydocstyle from 4.0.1 to 5.0.2. + - Update pytest from 5.1.1 to 5.4.1. + - Update pytest-django from 3.5.1 to 3.8.0. + - Update PyYAML from 5.1.2 to 5.3.1. + - Update Sphinx from 2.2.0 to 3.0.4. + +## 1.5.3 + +- Use default settings for split health checks. + ## 1.5.2 + - Split GCP health check URLs. ## 1.5.1 + - Update GCP health checks. ## 1.5.0 + - Add 7 new questions. - Fix test case where no argument was passed for the total of evens question. Fixes #101 - Fix error in example of the driver speed question. @@ -34,7 +74,7 @@ - Fix bug where whitespace of user code in attempt wasn't shown in admin interface. - Fix bug where sender's email address is not listed on contact us forms sent to admin. - General typo fixes and question clarifications. -- Dependencies changes: +- Dependency updates: - Update django-recaptcha from 2.0.4 to 2.0.5. - Update google-api-python-client from 1.7.10 to 1.7.11. - Update pydocstyle from 4.0.0 to 4.0.1. diff --git a/codewof/config/__init__.py b/codewof/config/__init__.py index 09ad8ff66..bcfe02ac3 100644 --- a/codewof/config/__init__.py +++ b/codewof/config/__init__.py @@ -1,6 +1,6 @@ """Configuration for Django system.""" -__version__ = "1.5.4" +__version__ = "2.0.0" __version_info__ = tuple( [ int(num) if num.isdigit() else num diff --git a/codewof/general/management/commands/sampledata.py b/codewof/general/management/commands/sampledata.py index 65ef7895c..31877f7ba 100644 --- a/codewof/general/management/commands/sampledata.py +++ b/codewof/general/management/commands/sampledata.py @@ -26,7 +26,7 @@ def add_arguments(self, parser): '--skip_backdate', action='store_true', help='skip backdate step', - ) + ) def handle(self, *args, **options): """Automatically called when the sampledata command is given.""" @@ -85,8 +85,8 @@ def handle(self, *args, **options): management.call_command('load_questions') print('Programming questions loaded.\n') - management.call_command('load_badges') - print('Achievement badges loaded.\n') + management.call_command('load_achievements') + print('Achievements loaded.\n') # Research StudyFactory.create_batch(size=5) @@ -97,8 +97,8 @@ def handle(self, *args, **options): AttemptFactory.create_batch(size=50) print('Attempts loaded.\n') - # Award points and badges + # Award points and achievements if not skip: - management.call_command('backdate_points_and_badges') + management.call_command('backdate_points_and_achievements') else: print('Ignoring backdate step as requested.\n') diff --git a/codewof/programming/admin.py b/codewof/programming/admin.py index 41367d4e7..71ee28edd 100644 --- a/codewof/programming/admin.py +++ b/codewof/programming/admin.py @@ -10,7 +10,7 @@ QuestionTypeParsons, QuestionTypeDebugging, Profile, - Badge, + Achievement, Earned, ) @@ -27,7 +27,7 @@ class TestCaseAttemptInline(admin.TabularInline): class EarnedInline(admin.TabularInline): - """Configuration to show earned badges inline within profile admin.""" + """Configuration to show earned achievements inline within profile admin.""" model = Earned extra = 1 @@ -42,19 +42,19 @@ class ProfileAdmin(admin.ModelAdmin): inlines = (EarnedInline, ) -class BadgeAdmin(admin.ModelAdmin): - """Configuration for displaying badges in admin.""" +class AchievementAdmin(admin.ModelAdmin): + """Configuration for displaying achievements in admin.""" - list_display = ('id_name', 'display_name', 'badge_tier') - list_filter = ['badge_tier'] + list_display = ('id_name', 'display_name', 'achievement_tier') + list_filter = ['achievement_tier'] ordering = ('id_name', ) class EarnedAdmin(admin.ModelAdmin): - """Configuration for displaying earned badges in admin.""" + """Configuration for displaying earned achievements in admin.""" - list_display = ('date', 'badge', 'profile') - list_filter = ['badge'] + list_display = ('date', 'achievement', 'profile') + list_filter = ['achievement'] ordering = ('-date', ) @@ -80,5 +80,5 @@ class Media: admin.site.register(QuestionTypeParsons) admin.site.register(QuestionTypeDebugging) admin.site.register(Profile, ProfileAdmin) -admin.site.register(Badge, BadgeAdmin) +admin.site.register(Achievement, AchievementAdmin) admin.site.register(Earned, EarnedAdmin) diff --git a/codewof/programming/codewof_utils.py b/codewof/programming/codewof_utils.py index ae6c8fe53..cc867d0bf 100644 --- a/codewof/programming/codewof_utils.py +++ b/codewof/programming/codewof_utils.py @@ -1,4 +1,8 @@ -"""Utility functions for codeWOF system. Involves points, badges, and backdating points and badges per user.""" +""" +Utility functions for codeWOF system. + +Involves points, achievements, and backdating points and achievements per user. +""" import datetime import json @@ -10,13 +14,12 @@ from programming.models import ( Profile, Attempt, - Badge, + Achievement, Earned, ) from django.http import JsonResponse logger = logging.getLogger(__name__) -del logging LOGGING = { 'version': 1, @@ -28,7 +31,7 @@ } # Number of points awarded for achieving each goal -POINTS_BADGE = 10 +POINTS_ACHIEVEMENT = 10 POINTS_SOLUTION = 10 @@ -115,126 +118,127 @@ def get_questions_answered_in_past_month(profile, user_attempts=None): return len(solved) -def check_badge_conditions(profile, user_attempts=None): +def check_achievement_conditions(profile, user_attempts=None): """ - Check if the user profile has earned new badges for their profile. + Check if the user profile has earned new achievements for their profile. - Checks if the user has received each available badge. If not, check if the user has earned these badges. Badges - available to be checked for are profile creation, number of attempts made, number of questions answered, and - number of days with consecutive attempts. + Checks if the user has received each available achievement. + If not, check if the user has earned these achievements. + Achievements available to be checked for are profile creation, number of attempts made, + number of questions answered, and number of days with consecutive attempts. - A badge will not be removed if the user had earned it before but now doesn't meet the conditions + An achievement will not be removed if the user had earned it before but now doesn't meet the conditions """ if user_attempts is None: user_attempts = Attempt.objects.filter(profile=profile) - badge_objects = Badge.objects.all() - earned_badges = profile.earned_badges.all() - new_badge_names = "" - new_badge_objects = [] + achievement_objects = Achievement.objects.all() + earned_achievements = profile.earned_achievements.all() + new_achievement_names = "" + new_achievement_objects = [] - # account creation badge + # account creation achievement try: - creation_badge = badge_objects.get(id_name="create-account") - if creation_badge not in earned_badges: + creation_achievement = achievement_objects.get(id_name="create-account") + if creation_achievement not in earned_achievements: # create a new account creation Earned.objects.create( profile=profile, - badge=creation_badge + achievement=creation_achievement ) - new_badge_names += creation_badge.display_name + "\n" - new_badge_objects.append(creation_badge) - except Badge.DoesNotExist: - logger.warning("No such badge: create-account") + new_achievement_names += creation_achievement.display_name + "\n" + new_achievement_objects.append(creation_achievement) + except Achievement.DoesNotExist: + logger.warning("No such achievement: create-account") pass - # check questions solved badges + # check questions solved achievements try: - question_badges = badge_objects.filter(id_name__contains="questions-solved") + question_achievements = achievement_objects.filter(id_name__contains="questions-solved") solved = user_attempts.filter(passed_tests=True).distinct('question__slug') - for question_badge in question_badges: - if question_badge not in earned_badges: - num_questions = int(question_badge.id_name.split("-")[2]) + for question_achievement in question_achievements: + if question_achievement not in earned_achievements: + num_questions = int(question_achievement.id_name.split("-")[2]) if len(solved) >= num_questions: Earned.objects.create( profile=profile, - badge=question_badge + achievement=question_achievement ) - new_badge_names += question_badge.display_name + "\n" - new_badge_objects.append(question_badge) + new_achievement_names += question_achievement.display_name + "\n" + new_achievement_objects.append(question_achievement) else: - # hasn't achieved the current badge tier so won't achieve any higher ones + # hasn't achieved the current achievement tier so won't achieve any higher ones break - except Badge.DoesNotExist: - logger.warning("No such badges: questions-solved") + except Achievement.DoesNotExist: + logger.warning("No such achievements: questions-solved") pass - # checked questions attempted badges + # checked questions attempted achievements try: - attempt_badges = badge_objects.filter(id_name__contains="attempts-made") + attempt_achievements = achievement_objects.filter(id_name__contains="attempts-made") attempted = user_attempts - for attempt_badge in attempt_badges: - if attempt_badge not in earned_badges: - num_questions = int(attempt_badge.id_name.split("-")[2]) + for attempt_achievement in attempt_achievements: + if attempt_achievement not in earned_achievements: + num_questions = int(attempt_achievement.id_name.split("-")[2]) if len(attempted) >= num_questions: Earned.objects.create( profile=profile, - badge=attempt_badge + achievement=attempt_achievement ) - new_badge_names += attempt_badge.display_name + "\n" - new_badge_objects.append(attempt_badge) + new_achievement_names += attempt_achievement.display_name + "\n" + new_achievement_objects.append(attempt_achievement) else: - # hasn't achieved the current badge tier so won't achieve any higher ones + # hasn't achieved the current achievement tier so won't achieve any higher ones break - except Badge.DoesNotExist: - logger.warning("No such badges: attempts-made") + except Achievement.DoesNotExist: + logger.warning("No such achievements: attempts-made") pass - # consecutive days logged in badges + # consecutive days logged in achievements num_consec_days = get_days_consecutively_answered(profile, user_attempts=user_attempts) - consec_badges = badge_objects.filter(id_name__contains="consecutive-days") - for consec_badge in consec_badges: - if consec_badge not in earned_badges: - n_days = int(consec_badge.id_name.split("-")[2]) + consec_achievements = achievement_objects.filter(id_name__contains="consecutive-days") + for consec_achievement in consec_achievements: + if consec_achievement not in earned_achievements: + n_days = int(consec_achievement.id_name.split("-")[2]) if n_days <= num_consec_days: Earned.objects.create( profile=profile, - badge=consec_badge + achievement=consec_achievement ) - new_badge_names += consec_badge.display_name + "\n" - new_badge_objects.append(consec_badge) + new_achievement_names += consec_achievement.display_name + "\n" + new_achievement_objects.append(consec_achievement) else: - # hasn't achieved the current badge tier so won't achieve any higher ones + # hasn't achieved the current achievement tier so won't achieve any higher ones break - new_points = calculate_badge_points(new_badge_objects) + new_points = calculate_achievement_points(new_achievement_objects) profile.points += new_points profile.full_clean() profile.save() - return new_badge_names + return new_achievement_names -def calculate_badge_points(badges): - """Return the number of points earned by the user for new badges.""" +def calculate_achievement_points(achievements): + """Return the number of points earned by the user for new achievements.""" points = 0 - for badge in badges: - points += badge.badge_tier * POINTS_BADGE + for achievement in achievements: + points += achievement.achievement_tier * POINTS_ACHIEVEMENT return points def backdate_user(profile): """Perform backdate of a single user profile.""" attempts = Attempt.objects.filter(profile=profile) - profile = backdate_badges(profile, user_attempts=attempts) + profile = backdate_achievements(profile, user_attempts=attempts) profile = backdate_points(profile, user_attempts=attempts) profile.has_backdated = True profile.full_clean() profile.save() -def backdate_points_and_badges(n=-1, ignoreFlags=True): - """Perform batch backdate of all points and badges for n profiles in the system.""" - backdate_badges_times = [] +def backdate_points_and_achievements(n=-1, ignoreFlags=True): + """Perform batch backdate of all points and achievements for n profiles in the system.""" + backdate_achievements_times = [] backdate_points_times = [] time_before = time.perf_counter() profiles = Profile.objects.all() @@ -251,10 +255,10 @@ def backdate_points_and_badges(n=-1, ignoreFlags=True): if not profile.has_backdated or ignoreFlags: attempts = all_attempts.filter(profile=profile) - badges_time_before = time.perf_counter() - profile = backdate_badges(profile, user_attempts=attempts) - badges_time_after = time.perf_counter() - backdate_badges_times.append(badges_time_after - badges_time_before) + achievements_time_before = time.perf_counter() + profile = backdate_achievements(profile, user_attempts=attempts) + achievements_time_after = time.perf_counter() + backdate_achievements_times.append(achievements_time_after - achievements_time_before) points_time_before = time.perf_counter() profile = backdate_points(profile, user_attempts=attempts) @@ -271,9 +275,9 @@ def backdate_points_and_badges(n=-1, ignoreFlags=True): duration = time_after - time_before - if len(backdate_badges_times) > 0 and len(backdate_points_times) > 0: - badges_ave = statistics.mean(backdate_badges_times) - logger.debug(f"Average time per user to backdate badges: {badges_ave:0.4f} seconds") + if len(backdate_achievements_times) > 0 and len(backdate_points_times) > 0: + achievements_ave = statistics.mean(backdate_achievements_times) + logger.debug(f"Average time per user to backdate achievements: {achievements_ave:0.4f} seconds") points_ave = statistics.mean(backdate_points_times) logger.debug(f"Average time per user to backdate points: {points_ave:0.4f} seconds") @@ -293,12 +297,12 @@ def backdate_points(profile, user_attempts=None): num_correct_attempts = len(user_attempts.filter(passed_tests=True).distinct('question__slug')) profile.points = num_correct_attempts * POINTS_SOLUTION - for badge in profile.earned_badges.all(): - profile.points += POINTS_BADGE * badge.badge_tier + for achievement in profile.earned_achievements.all(): + profile.points += POINTS_ACHIEVEMENT * achievement.achievement_tier return profile -def backdate_badges(profile, user_attempts=None): - """Re-check the profile for badges earned.""" - check_badge_conditions(profile, user_attempts=user_attempts) +def backdate_achievements(profile, user_attempts=None): + """Re-check the profile for achievements earned.""" + check_achievement_conditions(profile, user_attempts=user_attempts) return profile diff --git a/codewof/programming/management/commands/backdate_points_and_badges.py b/codewof/programming/management/commands/backdate_points_and_achievements.py similarity index 77% rename from codewof/programming/management/commands/backdate_points_and_badges.py rename to codewof/programming/management/commands/backdate_points_and_achievements.py index 41e2730ae..265f96df8 100644 --- a/codewof/programming/management/commands/backdate_points_and_badges.py +++ b/codewof/programming/management/commands/backdate_points_and_achievements.py @@ -1,7 +1,7 @@ -"""Module for the custom Django backdate_points_and_badges command.""" +"""Module for the custom Django backdate_points_and_achievements command.""" from django.core.management.base import BaseCommand -from programming.codewof_utils import backdate_points_and_badges +from programming.codewof_utils import backdate_points_and_achievements class Command(BaseCommand): @@ -24,9 +24,9 @@ def add_arguments(self, parser): def handle(self, *args, **options): """Automatically called when the backdate command is given.""" - print("Backdating points and badges\n") + print("Backdating points and achievements\n") ignoreFlags = options['ignore_flags'] number = int(options['profiles']) if ignoreFlags and number > 0: raise ValueError("If ignoring backdate flags you must backdate all profiles.") - backdate_points_and_badges(number, ignoreFlags) + backdate_points_and_achievements(number, ignoreFlags) diff --git a/codewof/programming/management/commands/load_badges.py b/codewof/programming/management/commands/load_achievements.py similarity index 55% rename from codewof/programming/management/commands/load_badges.py rename to codewof/programming/management/commands/load_achievements.py index 557555125..83ddcbd51 100644 --- a/codewof/programming/management/commands/load_badges.py +++ b/codewof/programming/management/commands/load_achievements.py @@ -1,148 +1,148 @@ -"""Module for the custom Django load_badges command.""" +"""Module for the custom Django load_achievements command.""" from django.core.management.base import BaseCommand -from programming.models import Badge +from programming.models import Achievement # TODO: Consider relocating to a yaml file like the questions -BADGES = [ +ACHIEVEMENTS = [ { 'id_name': 'create-account', 'display_name': 'Created an account!', 'description': 'Created your very own account', - 'icon_name': 'img/icons/badges/icons8-badge-create-account-48.png', - 'badge_tier': 0, + 'icon_name': 'img/icons/achievements/icons8-achievement-create-account-48.png', + 'achievement_tier': 0, 'parent': None }, { 'id_name': 'questions-solved-100', 'display_name': 'Solved one hundred questions!', 'description': 'Solved one hundred questions', - 'icon_name': 'img/icons/badges/icons8-question-solved-gold-50.png', - 'badge_tier': 4, + 'icon_name': 'img/icons/achievements/icons8-question-solved-gold-50.png', + 'achievement_tier': 4, 'parent': None }, { 'id_name': 'questions-solved-10', 'display_name': 'Solved ten questions!', 'description': 'Solved ten questions', - 'icon_name': 'img/icons/badges/icons8-question-solved-silver-50.png', - 'badge_tier': 3, + 'icon_name': 'img/icons/achievements/icons8-question-solved-silver-50.png', + 'achievement_tier': 3, 'parent': 'questions-solved-100' }, { 'id_name': 'questions-solved-5', 'display_name': 'Solved five questions!', 'description': 'Solved five questions', - 'icon_name': 'img/icons/badges/icons8-question-solved-bronze-50.png', - 'badge_tier': 2, + 'icon_name': 'img/icons/achievements/icons8-question-solved-bronze-50.png', + 'achievement_tier': 2, 'parent': 'questions-solved-10' }, { 'id_name': 'questions-solved-1', 'display_name': 'Solved one question!', 'description': 'Solved your very first question', - 'icon_name': 'img/icons/badges/icons8-question-solved-black-50.png', - 'badge_tier': 1, + 'icon_name': 'img/icons/achievements/icons8-question-solved-black-50.png', + 'achievement_tier': 1, 'parent': 'questions-solved-5' }, { 'id_name': 'attempts-made-100', 'display_name': 'Made one hundred question attempts!', 'description': 'Attempted one hundred questions', - 'icon_name': 'img/icons/badges/icons8-attempt-made-gold-50.png', - 'badge_tier': 4, + 'icon_name': 'img/icons/achievements/icons8-attempt-made-gold-50.png', + 'achievement_tier': 4, 'parent': None }, { 'id_name': 'attempts-made-10', 'display_name': 'Made ten question attempts!', 'description': 'Attempted ten questions', - 'icon_name': 'img/icons/badges/icons8-attempt-made-silver-50.png', - 'badge_tier': 3, + 'icon_name': 'img/icons/achievements/icons8-attempt-made-silver-50.png', + 'achievement_tier': 3, 'parent': 'attempts-made-100' }, { 'id_name': 'attempts-made-5', 'display_name': 'Made five question attempts!', 'description': 'Attempted five questions', - 'icon_name': 'img/icons/badges/icons8-attempt-made-bronze-50.png', - 'badge_tier': 2, + 'icon_name': 'img/icons/achievements/icons8-attempt-made-bronze-50.png', + 'achievement_tier': 2, 'parent': 'attempts-made-10' }, { 'id_name': 'attempts-made-1', 'display_name': 'Made your first question attempt!', 'description': 'Attempted one question', - 'icon_name': 'img/icons/badges/icons8-attempt-made-black-50.png', - 'badge_tier': 1, + 'icon_name': 'img/icons/achievements/icons8-attempt-made-black-50.png', + 'achievement_tier': 1, 'parent': 'attempts-made-5' }, { 'id_name': 'consecutive-days-28', 'display_name': 'Worked on coding every day for four weeks!', 'description': 'Attempted at least one question every day for four weeks', - 'icon_name': 'img/icons/badges/icons8-calendar-28-50.png', - 'badge_tier': 5, + 'icon_name': 'img/icons/achievements/icons8-calendar-28-50.png', + 'achievement_tier': 5, 'parent': None }, { 'id_name': 'consecutive-days-21', 'display_name': 'Worked on coding every day for three weeks!', 'description': 'Attempted at least one question every day for three weeks', - 'icon_name': 'img/icons/badges/icons8-calendar-21-50.png', - 'badge_tier': 4, + 'icon_name': 'img/icons/achievements/icons8-calendar-21-50.png', + 'achievement_tier': 4, 'parent': 'consecutive-days-28' }, { 'id_name': 'consecutive-days-14', 'display_name': 'Worked on coding every day for two weeks!', 'description': 'Attempted at least one question every day for two weeks', - 'icon_name': 'img/icons/badges/icons8-calendar-14-50.png', - 'badge_tier': 3, + 'icon_name': 'img/icons/achievements/icons8-calendar-14-50.png', + 'achievement_tier': 3, 'parent': 'consecutive-days-21' }, { 'id_name': 'consecutive-days-7', 'display_name': 'Worked on coding every day for one week!', 'description': 'Attempted at least one question every day for one week', - 'icon_name': 'img/icons/badges/icons8-calendar-7-50.png', - 'badge_tier': 2, + 'icon_name': 'img/icons/achievements/icons8-calendar-7-50.png', + 'achievement_tier': 2, 'parent': 'consecutive-days-14' }, { 'id_name': 'consecutive-days-2', 'display_name': 'Worked on coding for two days in a row!', 'description': 'Attempted at least one question two days in a row', - 'icon_name': 'img/icons/badges/icons8-calendar-2-50.png', - 'badge_tier': 1, + 'icon_name': 'img/icons/achievements/icons8-calendar-2-50.png', + 'achievement_tier': 1, 'parent': 'consecutive-days-7' }, ] class Command(BaseCommand): - """Required command class for the custom Django load_badges command. + """Required command class for the custom Django load_achievements command. Future plan: Create full loader like the load_questions command """ - help = 'Loads badges into the database' + help = 'Loads achievements into the database' def handle(self, *args, **options): - """Automatically called when the load_badges command is given.""" - all_badges = {} + """Automatically called when the load_achievements command is given.""" + all_achievements = {} - for badge in BADGES: - all_badges[badge['id_name']], created = Badge.objects.update_or_create( - id_name=badge['id_name'], + for achievement in ACHIEVEMENTS: + all_achievements[achievement['id_name']], created = Achievement.objects.update_or_create( + id_name=achievement['id_name'], defaults={ - 'display_name': badge['display_name'], - 'description': badge['description'], - 'icon_name': badge['icon_name'], - 'badge_tier': badge['badge_tier'], - 'parent': None if badge['parent'] is None else all_badges[badge['parent']] + 'display_name': achievement['display_name'], + 'description': achievement['description'], + 'icon_name': achievement['icon_name'], + 'achievement_tier': achievement['achievement_tier'], + 'parent': None if achievement['parent'] is None else all_achievements[achievement['parent']] } ) - print("{} badge: {}".format("Created" if created else "Updated", badge['id_name'])) + print("{} achievement: {}".format("Created" if created else "Updated", achievement['id_name'])) - print("{} badges loaded!\n".format(len(all_badges))) + print("{} achievements loaded!\n".format(len(all_achievements))) diff --git a/codewof/programming/management/commands/raise_backdate_flags.py b/codewof/programming/management/commands/raise_backdate_flags.py index d65e208d0..48906e182 100644 --- a/codewof/programming/management/commands/raise_backdate_flags.py +++ b/codewof/programming/management/commands/raise_backdate_flags.py @@ -17,4 +17,4 @@ def handle(self, *args, **options): profile.has_backdated = False profile.full_clean() profile.save() - print("done\n") + print("Completed raising backdate flags\n") diff --git a/codewof/programming/migrations/0012_auto_20200608_1252.py b/codewof/programming/migrations/0012_auto_20200608_1252.py new file mode 100644 index 000000000..bd475d2d6 --- /dev/null +++ b/codewof/programming/migrations/0012_auto_20200608_1252.py @@ -0,0 +1,40 @@ +# Generated by Django 2.2.3 on 2020-06-08 00:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('programming', '0011_auto_20200519_1230'), + ] + + operations = [ + migrations.RenameModel( + old_name='Badge', + new_name='Achievement', + ), + migrations.AlterModelOptions( + name='achievement', + options={'ordering': ['achievement_tier']}, + ), + migrations.AlterModelOptions( + name='earned', + options={'verbose_name': 'Earned achievement', 'verbose_name_plural': 'Achievements earned'}, + ), + migrations.RenameField( + model_name='achievement', + old_name='badge_tier', + new_name='achievement_tier', + ), + migrations.RenameField( + model_name='earned', + old_name='badge', + new_name='achievement', + ), + migrations.RenameField( + model_name='profile', + old_name='earned_badges', + new_name='earned_achievements', + ), + ] diff --git a/codewof/programming/models.py b/codewof/programming/models.py index cd70e6b2c..d8d55e113 100644 --- a/codewof/programming/models.py +++ b/codewof/programming/models.py @@ -25,7 +25,7 @@ class Profile(models.Model): default=1, validators=[MinValueValidator(1), MaxValueValidator(7)] ) - earned_badges = models.ManyToManyField('Badge', through='Earned') + earned_achievements = models.ManyToManyField('Achievement', through='Earned') attempted_questions = models.ManyToManyField('Question', through='Attempt') has_backdated = models.BooleanField(default=False) @@ -49,38 +49,38 @@ def save_user_profile(sender, instance, **kwargs): instance.profile.save() -class Badge(models.Model): - """Badge that can be earned by a user.""" +class Achievement(models.Model): + """Achievement that can be earned by a user.""" id_name = models.CharField(max_length=SMALL, unique=True) display_name = models.CharField(max_length=SMALL) description = models.CharField(max_length=LARGE) icon_name = models.CharField(null=True, max_length=SMALL) - badge_tier = models.IntegerField(default=0) - parent = models.ForeignKey('Badge', on_delete=models.CASCADE, null=True, default=None) + achievement_tier = models.IntegerField(default=0) + parent = models.ForeignKey('Achievement', on_delete=models.CASCADE, null=True, default=None) def __str__(self): - """Text representation of a badge.""" + """Text representation of an achievement.""" return self.display_name class Meta: - """Queryset will be ordered by badge tier.""" + """Queryset will be ordered by achievement tier.""" - ordering = ['badge_tier'] + ordering = ['achievement_tier'] class Earned(models.Model): - """Model that documents when a badge is earned by a user in their profile.""" + """Model that documents when an achievement is earned by a user in their profile.""" profile = models.ForeignKey('Profile', on_delete=models.CASCADE) - badge = models.ForeignKey('Badge', on_delete=models.CASCADE) + achievement = models.ForeignKey('Achievement', on_delete=models.CASCADE) date = models.DateTimeField(default=timezone.now) class Meta: """How the name is displayed in the Admin view.""" - verbose_name = "Earned badge" - verbose_name_plural = "Badges earned" + verbose_name = "Earned achievement" + verbose_name_plural = "Achievements earned" def __str__(self): """Text representation of an Earned object.""" diff --git a/codewof/programming/views.py b/codewof/programming/views.py index 665a64627..3c3e9f69e 100644 --- a/codewof/programming/views.py +++ b/codewof/programming/views.py @@ -26,7 +26,7 @@ ) from research.models import StudyRegistration -from programming.codewof_utils import add_points, check_badge_conditions +from programming.codewof_utils import add_points, check_achievement_conditions QUESTION_JAVASCRIPT = 'js/question_types/{}.js' BATCH_SIZE = 15 @@ -179,11 +179,11 @@ def save_question_attempt(request): result['success'] = True points_before = profile.points points = add_points(question, profile, attempt) - badges = check_badge_conditions(profile) + achievements = check_achievement_conditions(profile) points_after = profile.points result['curr_points'] = points result['point_diff'] = points_after - points_before - result['badges'] = badges + result['achievements'] = achievements else: result['success'] = False result['message'] = 'Attempt not saved, same as previous attempt.' @@ -199,7 +199,7 @@ def partial_backdate(request): # https://cloud.google.com/appengine/docs/standard/python3/scheduling-jobs-with-cron-yaml?hl=en_US # #validating_cron_requests if settings.DEBUG or 'X-Appengine-Cron' in request.headers: - management.call_command("backdate_points_and_badges", profiles=BATCH_SIZE) + management.call_command("backdate_points_and_achievements", profiles=BATCH_SIZE) response = { 'success': True, } diff --git a/codewof/static/img/icons/badges/icons8-badge-create-account-48.png b/codewof/static/img/icons/achievements/icons8-achievement-create-account-48.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-badge-create-account-48.png rename to codewof/static/img/icons/achievements/icons8-achievement-create-account-48.png diff --git a/codewof/static/img/icons/badges/icons8-attempt-made-black-50.png b/codewof/static/img/icons/achievements/icons8-attempt-made-black-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-attempt-made-black-50.png rename to codewof/static/img/icons/achievements/icons8-attempt-made-black-50.png diff --git a/codewof/static/img/icons/badges/icons8-attempt-made-bronze-50.png b/codewof/static/img/icons/achievements/icons8-attempt-made-bronze-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-attempt-made-bronze-50.png rename to codewof/static/img/icons/achievements/icons8-attempt-made-bronze-50.png diff --git a/codewof/static/img/icons/badges/icons8-attempt-made-gold-50.png b/codewof/static/img/icons/achievements/icons8-attempt-made-gold-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-attempt-made-gold-50.png rename to codewof/static/img/icons/achievements/icons8-attempt-made-gold-50.png diff --git a/codewof/static/img/icons/badges/icons8-attempt-made-silver-50.png b/codewof/static/img/icons/achievements/icons8-attempt-made-silver-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-attempt-made-silver-50.png rename to codewof/static/img/icons/achievements/icons8-attempt-made-silver-50.png diff --git a/codewof/static/img/icons/badges/icons8-calendar-14-50.png b/codewof/static/img/icons/achievements/icons8-calendar-14-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-calendar-14-50.png rename to codewof/static/img/icons/achievements/icons8-calendar-14-50.png diff --git a/codewof/static/img/icons/badges/icons8-calendar-2-50.png b/codewof/static/img/icons/achievements/icons8-calendar-2-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-calendar-2-50.png rename to codewof/static/img/icons/achievements/icons8-calendar-2-50.png diff --git a/codewof/static/img/icons/badges/icons8-calendar-21-50.png b/codewof/static/img/icons/achievements/icons8-calendar-21-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-calendar-21-50.png rename to codewof/static/img/icons/achievements/icons8-calendar-21-50.png diff --git a/codewof/static/img/icons/badges/icons8-calendar-28-50.png b/codewof/static/img/icons/achievements/icons8-calendar-28-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-calendar-28-50.png rename to codewof/static/img/icons/achievements/icons8-calendar-28-50.png diff --git a/codewof/static/img/icons/badges/icons8-calendar-7-50.png b/codewof/static/img/icons/achievements/icons8-calendar-7-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-calendar-7-50.png rename to codewof/static/img/icons/achievements/icons8-calendar-7-50.png diff --git a/codewof/static/img/icons/badges/icons8-question-solved-black-50.png b/codewof/static/img/icons/achievements/icons8-question-solved-black-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-question-solved-black-50.png rename to codewof/static/img/icons/achievements/icons8-question-solved-black-50.png diff --git a/codewof/static/img/icons/badges/icons8-question-solved-bronze-50.png b/codewof/static/img/icons/achievements/icons8-question-solved-bronze-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-question-solved-bronze-50.png rename to codewof/static/img/icons/achievements/icons8-question-solved-bronze-50.png diff --git a/codewof/static/img/icons/badges/icons8-question-solved-gold-50.png b/codewof/static/img/icons/achievements/icons8-question-solved-gold-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-question-solved-gold-50.png rename to codewof/static/img/icons/achievements/icons8-question-solved-gold-50.png diff --git a/codewof/static/img/icons/badges/icons8-question-solved-silver-50.png b/codewof/static/img/icons/achievements/icons8-question-solved-silver-50.png similarity index 100% rename from codewof/static/img/icons/badges/icons8-question-solved-silver-50.png rename to codewof/static/img/icons/achievements/icons8-question-solved-silver-50.png diff --git a/codewof/static/js/question_types/base.js b/codewof/static/js/question_types/base.js index 0f43247ea..98e2460b0 100644 --- a/codewof/static/js/question_types/base.js +++ b/codewof/static/js/question_types/base.js @@ -42,12 +42,12 @@ function update_gamification(data) { }); } - badges = data.badges; - if (badges.length > 0){ - $("#badge_toast_header").text("New badges!"); - $("#badge_toast_body").text(badges); + achievements = data.achievements; + if (achievements.length > 0){ + $("#achievement_toast_header").text("New achievements!"); + $("#achievement_toast_body").text(achievements); $(document).ready(function(){ - $("#badge_toast").toast('show', {delay: 5000}); + $("#achievement_toast").toast('show', {delay: 5000}); }); } diff --git a/codewof/static/scss/_homepage.scss b/codewof/static/scss/_homepage.scss index f60a7871e..f58f28bff 100644 --- a/codewof/static/scss/_homepage.scss +++ b/codewof/static/scss/_homepage.scss @@ -10,7 +10,7 @@ margin-bottom: 1rem; } - .badge { + .achievement { font-size: 1rem; } diff --git a/codewof/static/scss/_question-card.scss b/codewof/static/scss/_question-card.scss index 73d55eb26..d143a5070 100644 --- a/codewof/static/scss/_question-card.scss +++ b/codewof/static/scss/_question-card.scss @@ -22,8 +22,8 @@ .qc-card { display: grid; grid-template-areas: - "qc-checkbox qc-type qc-icon" - "qc-checkbox qc-title qc-icon"; + "qc-checkbox qc-type" + "qc-checkbox qc-title"; grid-template-columns: auto 1fr auto; grid-template-rows: auto auto; border: 3px solid grey; @@ -56,10 +56,6 @@ margin-right: 0.5rem; } -.qc-icon { - grid-area: qc-icon; -} - .qc-type { align-self: end; grid-area: qc-type; diff --git a/codewof/static/scss/website.scss b/codewof/static/scss/website.scss index e2f84e315..cc4d9863c 100644 --- a/codewof/static/scss/website.scss +++ b/codewof/static/scss/website.scss @@ -171,24 +171,24 @@ strong { max-height: 5rem; } -// Create badge CSS rules off colour names +// Create achievement CSS rules off colour names @each $color, $value in $colors { - .badge-#{$color} { + .achievement-#{$color} { @include badge-variant($value); } } -.badge-container { +.achievement-container { clear: both; padding-bottom: 0.5rem; } -.badge-icon { +.achievement-icon { padding: 0.3125rem; float: left; } -.badge-icon-unachieved { +.achievement-icon-unachieved { padding: 0.3125rem; opacity: 0.2; } diff --git a/codewof/templates/account/email.html b/codewof/templates/account/email.html index 24c0fae8f..c853f0425 100644 --- a/codewof/templates/account/email.html +++ b/codewof/templates/account/email.html @@ -24,12 +24,12 @@

{% trans "E-mail Addresses" %}

{{ emailaddress.email }} {% if emailaddress.verified %} - {% trans "Verified" %} + {% trans "Verified" %} {% else %} - {% trans "Unverified" %} + {% trans "Unverified" %} {% endif %} {% if emailaddress.primary %} - {% trans "Primary" %} + {% trans "Primary" %} {% endif %} diff --git a/codewof/templates/base.html b/codewof/templates/base.html index d98dfea97..f8a79c614 100644 --- a/codewof/templates/base.html +++ b/codewof/templates/base.html @@ -46,7 +46,7 @@ {% if DEPLOYMENT_TYPE == 'dev' %} - Test server + Test server {% endif %}
-