Skip to content

Commit

Permalink
Merge pull request #1494 from gustavomm19/marketing-cohorts
Browse files Browse the repository at this point in the history
Add micro cohorts to cohort model
  • Loading branch information
jefer94 authored Dec 3, 2024
2 parents 5b1b2c3 + b8ff2ca commit b538f05
Show file tree
Hide file tree
Showing 14 changed files with 439 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Generated by Django 5.1.2 on 2024-11-07 22:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("admissions", "0064_academy_legal_name"),
]

operations = [
migrations.AddField(
model_name="cohort",
name="cohorts_order",
field=models.CharField(
blank=True,
default=None,
help_text="An IDs comma separated list to indicate the order in which the micro cohorts will be displayed",
max_length=50,
null=True,
),
),
migrations.AddField(
model_name="cohort",
name="micro_cohorts",
field=models.ManyToManyField(
blank=True,
help_text="This cohorts will represent small courses inside a main course",
related_name="cohorts",
to="admissions.cohort",
),
),
]
24 changes: 24 additions & 0 deletions breathecode/admissions/migrations/0066_cohort_color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.1.2 on 2024-11-14 12:32

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("admissions", "0065_cohort_cohorts_order_cohort_micro_cohorts"),
]

operations = [
migrations.AddField(
model_name="cohort",
name="color",
field=models.CharField(
blank=True,
default=None,
help_text="Add the color with hexadecimal format, i.e.: #FFFFFF",
max_length=50,
null=True,
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2024-11-15 09:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("admissions", "0066_cohort_color"),
]

operations = [
migrations.AlterField(
model_name="cohort",
name="micro_cohorts",
field=models.ManyToManyField(
blank=True,
help_text="This cohorts will represent small courses inside a main course",
related_name="main_cohorts",
to="admissions.cohort",
),
),
]
22 changes: 22 additions & 0 deletions breathecode/admissions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,28 @@ class Cohort(models.Model):

language = models.CharField(max_length=2, default="en", db_index=True)

micro_cohorts = models.ManyToManyField(
"Cohort",
blank=True,
help_text="This cohorts will represent small courses inside a main course",
related_name="main_cohorts",
)

cohorts_order = models.CharField(
max_length=50,
null=True,
blank=True,
default=None,
help_text="An IDs comma separated list to indicate the order in which the micro cohorts will be displayed",
)
color = models.CharField(
max_length=50,
null=True,
blank=True,
default=None,
help_text="Add the color with hexadecimal format, i.e.: #FFFFFF",
)

created_at = models.DateTimeField(auto_now_add=True, editable=False)
updated_at = models.DateTimeField(auto_now=True, editable=False)

Expand Down
48 changes: 44 additions & 4 deletions breathecode/admissions/receivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import re
from typing import Any, Type
from asgiref.sync import sync_to_async

from django.dispatch import receiver

Expand All @@ -12,7 +13,7 @@

from ..activity import tasks as activity_tasks
from .models import Cohort, CohortUser
from .signals import cohort_log_saved, cohort_user_created
from .signals import cohort_log_saved, cohort_user_created, student_edu_status_updated

# add your receives here
logger = logging.getLogger(__name__)
Expand All @@ -26,9 +27,23 @@ def process_cohort_history_log(sender: Type[Cohort], instance: Cohort, **kwargs:
activity_tasks.get_attendancy_log.delay(instance.id)


@receiver(cohort_user_created, sender=Cohort)
async def new_cohort_user(sender: Type[Cohort], instance: Cohort, **kwargs: Any):
logger.info("Processing Cohort history log for cohort: " + str(instance.id))
@sync_to_async
def join_to_micro_cohorts(cohort_user):

micro_cohorts = cohort_user.cohort.micro_cohorts.all()

user = cohort_user.user
for cohort in micro_cohorts:
cohort_user = CohortUser.objects.filter(user=user, cohort=cohort, role="STUDENT").first()
if cohort_user is None:
cohort_user = CohortUser(user=user, cohort=cohort, role="STUDENT", finantial_status="FULLY_PAID")
cohort_user.save()


@receiver(cohort_user_created, sender=CohortUser)
async def new_cohort_user(sender: Type[CohortUser], instance: CohortUser, **kwargs: Any):
logger.info("Signal for created cohort user: " + str(instance.id))
await join_to_micro_cohorts(instance)

await authenticate_actions.send_webhook(
"rigobot",
Expand Down Expand Up @@ -71,6 +86,31 @@ def schedule_repository_deletion(sender: Type[Task], instance: Task, **kwargs: A
order.save()


@receiver(student_edu_status_updated, sender=CohortUser)
def post_save_cohort_user(sender: Type[CohortUser], instance: CohortUser, **kwargs: Any):
logger.info("Validating if the student is graduating from a saas cohort")
cohort = instance.cohort

if instance.cohort is None:
return

if cohort.available_as_saas and instance.educational_status == "GRADUATED":
# main_cohorts is the backwards relationship for the many to many
# it contains every cohort that another cohort is linked to as a micro cohort
main_cohorts = cohort.main_cohorts.all()
for main in main_cohorts:
main_cohort_user = CohortUser.objects.filter(cohort=main, user=instance.user).first()
if main_cohort_user.educational_status != "GRADUATED":
main_cohort = main_cohort_user.cohort
micro_cohorts = main_cohort.micro_cohorts.all()
cohort_users = CohortUser.objects.filter(user=instance.user, cohort__in=micro_cohorts).exclude(
educational_status__in=["GRADUATED"]
)
if len(cohort_users) == 0:
main_cohort_user.educational_status = "GRADUATED"
main_cohort_user.save()


@receiver(revision_status_updated, sender=Task, weak=False)
def mark_saas_student_as_graduated(sender: Type[Task], instance: Task, **kwargs: Any):
logger.info("Processing available as saas student's tasks and marking as GRADUATED if it is")
Expand Down
16 changes: 16 additions & 0 deletions breathecode/admissions/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
logger = logging.getLogger(__name__)


class GetTinyCohortSerializer(serpy.Serializer):
"""The serializer schema definition."""

# Use a Field subclass like IntField if you need more validation.
id = serpy.Field()
name = serpy.Field()
slug = serpy.Field()


class CountrySerializer(serpy.Serializer):
"""The serializer schema definition."""

Expand Down Expand Up @@ -433,15 +442,22 @@ class GetMeCohortSerializer(serpy.Serializer):
name = serpy.Field()
kickoff_date = serpy.Field()
ending_date = serpy.Field()
micro_cohorts = serpy.MethodField()
cohorts_order = serpy.Field()
intro_video = serpy.Field()
current_day = serpy.Field()
color = serpy.Field()
current_module = serpy.Field()
syllabus_version = SyllabusVersionSmallSerializer(required=False)
academy = GetAcademySerializer()
stage = serpy.Field()
is_hidden_on_prework = serpy.Field()
available_as_saas = serpy.Field()

def get_micro_cohorts(self, obj):
cohorts = obj.micro_cohorts.all()
return GetTinyCohortSerializer(cohorts, many=True).data


class GetPublicCohortUserSerializer(serpy.Serializer):
user = UserPublicSerializer()
Expand Down
109 changes: 109 additions & 0 deletions breathecode/admissions/tests/receivers/tests_new_cohort_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import random

import pytest

from breathecode.tests.mixins.breathecode_mixin.breathecode import Breathecode


@pytest.fixture(autouse=True)
def arange(db, bc: Breathecode, fake):

yield


def test_with_one_micro_cohort(enable_signals, bc: Breathecode):
enable_signals()

model_micro_cohort = bc.database.create(
cohort={"available_as_saas": True},
)

model_main_cohort = bc.database.create(
user=1,
cohort_user={"role": "STUDENT"},
cohort={"available_as_saas": True, "micro_cohorts": [model_micro_cohort.cohort]},
)

assert bc.database.list_of("admissions.CohortUser") == [
{
**bc.format.to_dict(model_main_cohort.cohort_user),
},
{
**bc.format.to_dict(model_main_cohort.cohort_user),
"id": 2,
"cohort_id": 1,
"finantial_status": "FULLY_PAID",
},
]


def test_with_many_micro_cohorts(enable_signals, bc: Breathecode):
enable_signals()

model_micro_cohort = bc.database.create(
cohort=[{"available_as_saas": True}, {"available_as_saas": True}],
)

model_main_cohort = bc.database.create(
user=1,
cohort_user={"role": "STUDENT"},
cohort={
"available_as_saas": True,
"micro_cohorts": [model_micro_cohort.cohort[0], model_micro_cohort.cohort[1]],
},
)

assert bc.database.list_of("admissions.CohortUser") == [
{
**bc.format.to_dict(model_main_cohort.cohort_user),
},
{
**bc.format.to_dict(model_main_cohort.cohort_user),
"id": 2,
"cohort_id": 1,
"finantial_status": "FULLY_PAID",
},
{
**bc.format.to_dict(model_main_cohort.cohort_user),
"id": 3,
"cohort_id": 2,
"finantial_status": "FULLY_PAID",
},
]


def test_with_cohort_users_previously_created(enable_signals, bc: Breathecode):
enable_signals()

model_micro_cohorts = bc.database.create(
cohort=[{"available_as_saas": True}, {"available_as_saas": True}],
)

model_cohort_users = bc.database.create(
user=1,
cohort_user=[
{"role": "STUDENT", "cohort": model_micro_cohorts.cohort[0]},
{"role": "STUDENT", "cohort": model_micro_cohorts.cohort[1]},
],
)

model_main_cohort = bc.database.create(
cohort_user={"user": model_cohort_users.user, "role": "STUDENT"},
cohort={"available_as_saas": True, "micro_cohorts": [*model_micro_cohorts.cohort]},
)

assert bc.database.list_of("admissions.CohortUser") == [
{
**bc.format.to_dict(model_main_cohort.cohort_user),
"id": 1,
"cohort_id": 1,
},
{
**bc.format.to_dict(model_main_cohort.cohort_user),
"id": 2,
"cohort_id": 2,
},
{
**bc.format.to_dict(model_main_cohort.cohort_user),
},
]
Loading

0 comments on commit b538f05

Please sign in to comment.