diff --git a/breathecode/admissions/views.py b/breathecode/admissions/views.py index e4bb9d3de..01b8f557b 100644 --- a/breathecode/admissions/views.py +++ b/breathecode/admissions/views.py @@ -1834,6 +1834,9 @@ def get(self, request, syllabus_id, version, academy_id): # Write the data rows for each day for day in sorted(syllabus_version.json["days"], key=lambda x: x["position"]): week_number = math.ceil(cumulative_days / class_days_per_week) + if "technologies" not in day: + day["technologies"] = [] + if lang == "es": writer.writerow( [ @@ -1856,7 +1859,7 @@ def get(self, request, syllabus_id, version, academy_id): day.get("teacher_instructions", ""), ] ) - cumulative_days += day["duration_in_days"] + cumulative_days += day["duration_in_days"] if "duration_in_days" in day else 1 return response diff --git a/breathecode/assessment/models.py b/breathecode/assessment/models.py index cc0fa6158..dd945236f 100644 --- a/breathecode/assessment/models.py +++ b/breathecode/assessment/models.py @@ -294,14 +294,20 @@ def __init__(self, *args, **kwargs): self._old_status = self.status def save(self, *args, **kwargs): + + is_creating = self.pk is None if not self.pk: self.token = binascii.hexlify(os.urandom(20)).decode() + _instance = super().save(*args, **kwargs) + # Answer is being closed - if self.status != self._old_status: + if is_creating or self.status != self._old_status: signals.userassessment_status_updated.send_robust(instance=self, sender=self.__class__) - return super().save(*args, **kwargs) + return _instance + + def get_score(self): diff --git a/breathecode/assessment/serializers.py b/breathecode/assessment/serializers.py index e7f46a9f2..d2acfacc9 100644 --- a/breathecode/assessment/serializers.py +++ b/breathecode/assessment/serializers.py @@ -162,7 +162,6 @@ class HookUserAssessmentSerializer(serpy.Serializer): status_text = serpy.Field() conversion_info = serpy.Field() - total_score = serpy.Field() comment = serpy.Field() started_at = serpy.Field() @@ -170,6 +169,16 @@ class HookUserAssessmentSerializer(serpy.Serializer): created_at = serpy.Field() + summary = serpy.MethodField() + def get_summary(self, obj): + total_score, last_one = obj.get_score() + + last_answer = None + if last_one is not None: + last_answer = AnswerSmallSerializer(last_one).data + + return {"last_answer": last_answer, "live_score": total_score} + class PublicUserAssessmentSerializer(serpy.Serializer): id = serpy.Field() diff --git a/breathecode/feedback/admin.py b/breathecode/feedback/admin.py index 48427a50e..a533ab2ec 100644 --- a/breathecode/feedback/admin.py +++ b/breathecode/feedback/admin.py @@ -1,19 +1,20 @@ -import logging import json +import logging + from django.contrib import admin, messages from django.contrib.auth.admin import UserAdmin -from breathecode.admissions.admin import ( - CohortAdmin as AdmissionsCohortAdmin, - CohortUserAdmin as AdmissionsCohortUserAdmin, -) -from breathecode.feedback.tasks import recalculate_survey_scores -from .models import Answer, UserProxy, CohortProxy, CohortUserProxy, Survey, Review, ReviewPlatform -from .actions import send_survey_group, create_user_graduation_reviews -from . import actions from django.utils.html import format_html + +from breathecode.admissions.admin import CohortAdmin as AdmissionsCohortAdmin +from breathecode.admissions.admin import CohortUserAdmin as AdmissionsCohortUserAdmin +from breathecode.feedback.tasks import recalculate_survey_scores from breathecode.utils import AdminExportCsvMixin from breathecode.utils.admin import change_field +from . import actions +from .actions import create_user_graduation_reviews, send_survey_group +from .models import Answer, CohortProxy, CohortUserProxy, Review, ReviewPlatform, Survey, UserProxy + logger = logging.getLogger(__name__) @@ -181,7 +182,7 @@ class AnswerAdmin(admin.ModelAdmin, AdminExportCsvMixin): search_fields = ["user__first_name", "user__last_name", "user__email", "cohort__slug"] list_filter = [AnswerTypeFilter, "status", "score", "academy__slug", "cohort__slug"] actions = ["export_as_csv", add_academy_to_answer] - raw_id_fields = ["user", "cohort", "mentor", "event", "mentorship_session"] + raw_id_fields = ["user", "cohort", "mentor", "event", "mentorship_session", "survey"] def answer_url(self, obj): url = "https://nps.4geeks.com/" + str(obj.id) diff --git a/breathecode/registry/actions.py b/breathecode/registry/actions.py index a57a3112c..78589ccc0 100644 --- a/breathecode/registry/actions.py +++ b/breathecode/registry/actions.py @@ -1083,7 +1083,7 @@ def add_syllabus_translations(_json: dict): day_count = -1 for day in _json.get("days", []): - technologies = [] + unique_technologies = {} day_count += 1 for asset_type in ["assignments", "lessons", "quizzes", "replits"]: index = -1 @@ -1108,7 +1108,9 @@ def add_syllabus_translations(_json: dict): # add translations technologies as well _assetTechs = a.technologies.all() for t in _assetTechs: - technologies.append({"slug": t.slug, "title": t.title}) + # Use the slug as a unique key to avoid duplicates + if t.slug not in unique_technologies: + unique_technologies[t.slug] = {"slug": t.slug, "title": t.title} if _asset.lang not in _json["days"][day_count][asset_type][index]["translations"]: _json["days"][day_count][asset_type][index]["translations"][_asset.lang] = { @@ -1118,6 +1120,9 @@ def add_syllabus_translations(_json: dict): _assetTechs = _asset.technologies.all() for t in _assetTechs: - technologies.append({"slug": t.slug, "title": t.title}) - _json["days"][day_count]["technologies"] = technologies + # Use the slug as a unique key to avoid duplicates + if t.slug not in unique_technologies: + unique_technologies[t.slug] = {"slug": t.slug, "title": t.title} + + _json["days"][day_count]["technologies"] = list(unique_technologies.values()) return _json diff --git a/breathecode/registry/migrations/0047_asset_preview_in_tutorial_alter_asset_preview.py b/breathecode/registry/migrations/0047_asset_preview_in_tutorial_alter_asset_preview.py new file mode 100644 index 000000000..6b550f61d --- /dev/null +++ b/breathecode/registry/migrations/0047_asset_preview_in_tutorial_alter_asset_preview.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.1 on 2024-11-05 15:04 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registry", "0046_assetcontext"), + ] + + operations = [ + migrations.AddField( + model_name="asset", + name="preview_in_tutorial", + field=models.URLField( + blank=True, + default=None, + help_text="Used in 4geeks.com before the tutorial is about to start", + null=True, + ), + ), + migrations.AlterField( + model_name="asset", + name="preview", + field=models.URLField( + blank=True, default=None, help_text="This preview will be used when shared in social media", null=True + ), + ), + ] diff --git a/breathecode/registry/models.py b/breathecode/registry/models.py index 7f6b5767e..7ff009a35 100644 --- a/breathecode/registry/models.py +++ b/breathecode/registry/models.py @@ -308,7 +308,12 @@ def __init__(self, *args, **kwargs): url = models.URLField(null=True, blank=True, default=None) solution_url = models.URLField(null=True, blank=True, default=None) - preview = models.URLField(null=True, blank=True, default=None) + preview = models.URLField( + null=True, blank=True, default=None, help_text="This preview will be used when shared in social media" + ) + preview_in_tutorial = models.URLField( + null=True, blank=True, default=None, help_text="Used in 4geeks.com before the tutorial is about to start" + ) description = models.TextField(null=True, blank=True, default=None) requirements = models.TextField( null=True,