From 5a94582a749fd4ef750f8c4d203f1793d4a290bc Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 5 Jul 2024 21:14:13 -0500 Subject: [PATCH 1/2] fix mentorship survey, update signals, add vs extensions to codespaces, update hook manager serializer --- .devcontainer/devcontainer.json | 9 ++- breathecode/assessment/signals.py | 13 ++--- breathecode/assignments/admin.py | 27 ++++++++- breathecode/assignments/signals.py | 15 +++-- breathecode/authenticate/signals.py | 16 +++--- breathecode/feedback/apps.py | 3 +- breathecode/feedback/receivers.py | 6 +- breathecode/feedback/signals.py | 11 ++-- breathecode/feedback/tasks.py | 40 +++++++++---- breathecode/mentorship/models.py | 7 ++- breathecode/mentorship/signals.py | 11 +++- ...s.py => tests_mentorship_session_saved.py} | 56 +++++++++++++++---- .../tests/urls/tests_academy_session.py | 38 ------------- .../tests/urls/tests_academy_session_id.py | 46 --------------- breathecode/monitoring/signals.py | 13 ++--- breathecode/notify/utils/hook_manager.py | 17 +++--- breathecode/registry/signals.py | 17 +++--- 17 files changed, 172 insertions(+), 173 deletions(-) rename breathecode/mentorship/tests/signals/{tests_mentorship_session_status.py => tests_mentorship_session_saved.py} (83%) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 909f447ae..1c808940c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -25,9 +25,14 @@ "eamodio.gitlens", "gruntfuggly.todo-tree", "redhat.vscode-yaml", - "bungcip.better-toml", + "ms-python.black-formatter", + "ms-python.isort", + "janisdd.vscode-edit-csv", + "tamasfe.even-better-toml", + "ms-python.flake8", + "donjayamanne.githistory", "TabNine.tabnine-vscode", - "github.vscode-github-actions" + "ms-python.debugpy" ] } }, diff --git a/breathecode/assessment/signals.py b/breathecode/assessment/signals.py index 4a7057fff..33ab72ea9 100644 --- a/breathecode/assessment/signals.py +++ b/breathecode/assessment/signals.py @@ -1,9 +1,8 @@ -""" -For each signal you want other apps to be able to receive, you have to -declare a new variable here like this: -""" +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -from django import dispatch +from task_manager.django.dispatch import Emisor -assessment_updated = dispatch.Signal() -userassessment_status_updated = dispatch.Signal() +emisor = Emisor("breathecode.assessment") + +assessment_updated = emisor.signal("assessment_updated") +userassessment_status_updated = emisor.signal("userassessment_status_updated") diff --git a/breathecode/assignments/admin.py b/breathecode/assignments/admin.py index 24e474b0e..7df752ed7 100644 --- a/breathecode/assignments/admin.py +++ b/breathecode/assignments/admin.py @@ -10,7 +10,17 @@ from breathecode.services.learnpack import LearnPack from .actions import sync_student_tasks -from .models import AssignmentTelemetry, CohortProxy, FinalProject, LearnPackWebhook, Task, UserAttachment, UserProxy +from .models import ( + AssignmentTelemetry, + CohortProxy, + FinalProject, + LearnPackWebhook, + RepositoryDeletionOrder, + RepositoryWhiteList, + Task, + UserAttachment, + UserProxy, +) # Register your models here. logger = logging.getLogger(__name__) @@ -125,7 +135,6 @@ def async_process_hook(modeladmin, request, queryset): def process_hook(modeladmin, request, queryset): # stay this here for use the poor mocking system for hook in queryset.all().order_by("created_at"): - print(f"Procesing hook: {hook.id}") client = LearnPack() try: client.execute_action(hook.id) @@ -166,3 +175,17 @@ def from_status(s): return "" return format_html(f"
{obj.status}
{obj.status_text}") + + +@admin.register(RepositoryDeletionOrder) +class RepositoryDeletionOrderAdmin(admin.ModelAdmin): + list_display = ("provider", "status", "repository_user", "repository_name") + search_fields = ["repository_user", "repository_name"] + list_filter = ["provider", "status"] + + +@admin.register(RepositoryWhiteList) +class RepositoryWhiteListAdmin(admin.ModelAdmin): + list_display = ("provider", "repository_user", "repository_name") + search_fields = ["repository_user", "repository_name"] + list_filter = ["provider"] diff --git a/breathecode/assignments/signals.py b/breathecode/assignments/signals.py index e26e8e30c..f65505b4e 100644 --- a/breathecode/assignments/signals.py +++ b/breathecode/assignments/signals.py @@ -1,10 +1,9 @@ -""" -For each signal you want other apps to be able to receive, you have to -declare a new variable here like this: -""" +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -from django import dispatch +from task_manager.django.dispatch import Emisor -assignment_created = dispatch.Signal() -assignment_status_updated = dispatch.Signal() -revision_status_updated = dispatch.Signal() +emisor = Emisor("breathecode.assignments") + +assignment_created = emisor.signal("assignment_created") +assignment_status_updated = emisor.signal("assignment_status_updated") +revision_status_updated = emisor.signal("revision_status_updated") diff --git a/breathecode/authenticate/signals.py b/breathecode/authenticate/signals.py index 421846b9f..d49a34602 100644 --- a/breathecode/authenticate/signals.py +++ b/breathecode/authenticate/signals.py @@ -1,13 +1,15 @@ -from django import dispatch +from task_manager.django.dispatch import Emisor + +emisor = Emisor("breathecode.authenticate") # UserInvite accepted -invite_status_updated = dispatch.Signal() +invite_status_updated = emisor.signal("invite_status_updated") # ProfileAcademy accepted -academy_invite_accepted = dispatch.Signal() -profile_academy_saved = dispatch.Signal() +academy_invite_accepted = emisor.signal("academy_invite_accepted") +profile_academy_saved = emisor.signal("profile_academy_saved") # post_delete and post_save for User, ProfileAcademy and MentorProfileMentorProfile -user_info_updated = dispatch.Signal() -user_info_deleted = dispatch.Signal() +user_info_updated = emisor.signal("user_info_updated") +user_info_deleted = emisor.signal("user_info_deleted") -cohort_user_deleted = dispatch.Signal() +cohort_user_deleted = emisor.signal("cohort_user_deleted") diff --git a/breathecode/feedback/apps.py b/breathecode/feedback/apps.py index d11a79741..df27db632 100644 --- a/breathecode/feedback/apps.py +++ b/breathecode/feedback/apps.py @@ -1,4 +1,5 @@ import logging + from django.apps import AppConfig logger = logging.getLogger(__name__) @@ -8,5 +9,5 @@ class FeedbackConfig(AppConfig): name = "breathecode.feedback" def ready(self): - logger.debug("Loading feedback.receivers") from . import receivers # noqa: F401 + from . import supervisors # noqa: F401 diff --git a/breathecode/feedback/receivers.py b/breathecode/feedback/receivers.py index d0195f0b4..60b5bbb71 100644 --- a/breathecode/feedback/receivers.py +++ b/breathecode/feedback/receivers.py @@ -7,7 +7,7 @@ from breathecode.admissions.models import CohortUser from breathecode.admissions.signals import student_edu_status_updated from breathecode.mentorship.models import MentorshipSession -from breathecode.mentorship.signals import mentorship_session_status +from breathecode.mentorship.signals import mentorship_session_saved from .models import Answer from .signals import survey_answered @@ -33,9 +33,9 @@ def post_save_cohort_user(sender, instance, **kwargs): process_student_graduation.delay(instance.cohort.id, instance.user.id) -@receiver(mentorship_session_status, sender=MentorshipSession) +@receiver(mentorship_session_saved, sender=MentorshipSession) def post_mentorin_session_ended(sender: Type[MentorshipSession], instance: MentorshipSession, **kwargs): - if instance.status == "COMPLETED": + if instance.status == "COMPLETED" and Answer.objects.filter(mentorship_session__id=instance.id).exists() is False: duration = timedelta(seconds=0) if instance.started_at is not None and instance.ended_at is not None: duration = instance.ended_at - instance.started_at diff --git a/breathecode/feedback/signals.py b/breathecode/feedback/signals.py index 98272cfca..680b9ab05 100644 --- a/breathecode/feedback/signals.py +++ b/breathecode/feedback/signals.py @@ -1,9 +1,8 @@ -""" -For each signal you want other apps to be able to receive, you have to -declare a new variable here like this: -""" +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -from django import dispatch +from task_manager.django.dispatch import Emisor + +emisor = Emisor("breathecode.feedback") # when a student answers one particular questions of a survey -survey_answered = dispatch.Signal() +survey_answered = emisor.signal("survey_answered") diff --git a/breathecode/feedback/tasks.py b/breathecode/feedback/tasks.py index ac8711efb..ca78d64f5 100644 --- a/breathecode/feedback/tasks.py +++ b/breathecode/feedback/tasks.py @@ -2,7 +2,10 @@ from datetime import timedelta from django.contrib.auth.models import User +from django.core.cache import cache from django.utils import timezone +from django_redis import get_redis_connection +from redis.exceptions import LockError from task_manager.core.exceptions import AbortTask, RetryTask from task_manager.django.decorators import task @@ -11,6 +14,7 @@ from breathecode.mentorship.models import MentorshipSession from breathecode.notify import actions as notify_actions from breathecode.utils import TaskPriority, getLogger +from breathecode.utils.redis import Lock from capyc.rest_framework.exceptions import ValidationException from . import actions @@ -23,6 +27,7 @@ ADMIN_URL = os.getenv("ADMIN_URL", "") API_URL = os.getenv("API_URL", "") ENV = os.getenv("ENV", "") +IS_DJANGO_REDIS = hasattr(cache, "delete_pattern") def build_question(answer): @@ -291,19 +296,30 @@ def send_mentorship_session_survey(session_id, **_): if not session.service: raise AbortTask("Mentorship session doesn't have a service associated with it") - answer = Answer.objects.filter(mentorship_session__id=session.id).first() - if answer is None: - answer = Answer(mentorship_session=session, academy=session.mentor.academy, lang=session.service.language) - question = build_question(answer) - answer.title = question["title"] - answer.lowest = question["lowest"] - answer.highest = question["highest"] - answer.user = session.mentee - answer.status = "SENT" - answer.save() + client = None + if IS_DJANGO_REDIS: + client = get_redis_connection("default") - elif answer.status == "ANSWERED": - raise AbortTask(f"This survey about MentorshipSession {session.id} was answered") + try: + with Lock(client, f"lock:session:{session.id}:answer", timeout=30, blocking_timeout=30): + answer = Answer.objects.filter(mentorship_session__id=session.id).first() + if answer is None: + answer = Answer( + mentorship_session=session, academy=session.mentor.academy, lang=session.service.language + ) + question = build_question(answer) + answer.title = question["title"] + answer.lowest = question["lowest"] + answer.highest = question["highest"] + answer.user = session.mentee + answer.status = "SENT" + answer.save() + + elif answer.status == "ANSWERED": + raise AbortTask(f"This survey about MentorshipSession {session.id} was answered") + + except LockError: + raise RetryTask("Could not acquire lock for activity, operation timed out.") if not session.mentee.email: message = f"Author not have email, this survey cannot be send by {session.mentee.id}" diff --git a/breathecode/mentorship/models.py b/breathecode/mentorship/models.py index fe30a6408..70aaecb7a 100644 --- a/breathecode/mentorship/models.py +++ b/breathecode/mentorship/models.py @@ -468,10 +468,13 @@ def save(self, *args, **kwargs): is_creating = self.pk is None - super().save(*args, **kwargs) # Call the "real" save() method. + super().save(*args, **kwargs) if is_creating or self.__old_status != self.status: - signals.mentorship_session_status.send_robust(instance=self, sender=MentorshipSession) + signals.mentorship_session_status.delay(instance=self, sender=MentorshipSession) + + # we're testing why there hasn't any mentorship session with a survey changing mentorship_session_status by mentorship_session_saved + signals.mentorship_session_saved.delay(instance=self, sender=MentorshipSession) self.__old_status = self.status diff --git a/breathecode/mentorship/signals.py b/breathecode/mentorship/signals.py index 8c1ad770d..677c0b44a 100644 --- a/breathecode/mentorship/signals.py +++ b/breathecode/mentorship/signals.py @@ -1,4 +1,9 @@ -from django import dispatch +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -mentorship_session_status = dispatch.Signal() -mentor_profile_saved = dispatch.Signal() +from task_manager.django.dispatch import Emisor + +emisor = Emisor("breathecode.mentorship") + +mentorship_session_status = emisor.signal("mentorship_session_status") +mentor_profile_saved = emisor.signal("mentor_profile_saved") +mentorship_session_saved = emisor.signal("mentorship_session_saved") diff --git a/breathecode/mentorship/tests/signals/tests_mentorship_session_status.py b/breathecode/mentorship/tests/signals/tests_mentorship_session_saved.py similarity index 83% rename from breathecode/mentorship/tests/signals/tests_mentorship_session_status.py rename to breathecode/mentorship/tests/signals/tests_mentorship_session_saved.py index db5a52af4..03d7ad6bc 100644 --- a/breathecode/mentorship/tests/signals/tests_mentorship_session_status.py +++ b/breathecode/mentorship/tests/signals/tests_mentorship_session_saved.py @@ -1,5 +1,6 @@ from datetime import timedelta from unittest.mock import MagicMock, call, patch + from django.utils import timezone from breathecode.payments.tasks import refund_mentoring_session @@ -13,7 +14,7 @@ class TestLead(LegacyAPITestCase): @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_pending(self, enable_signals): + def test_with_status_pending(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -37,7 +38,7 @@ def test_mentorship_session_status__with_status_pending(self, enable_signals): @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_started(self, enable_signals): + def test_with_status_started(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -61,7 +62,7 @@ def test_mentorship_session_status__with_status_started(self, enable_signals): @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_failed(self, enable_signals): + def test_with_status_failed(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -81,7 +82,7 @@ def test_mentorship_session_status__with_status_failed(self, enable_signals): @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_failed__with_mentor_and_mentee(self, enable_signals): + def test_with_status_failed__with_mentor_and_mentee(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -107,7 +108,7 @@ def test_mentorship_session_status__with_status_failed__with_mentor_and_mentee(s @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_ignored(self, enable_signals): + def test_with_status_ignored(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -127,7 +128,7 @@ def test_mentorship_session_status__with_status_ignored(self, enable_signals): @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_ignored__with_mentor_and_mentee(self, enable_signals): + def test_with_status_ignored__with_mentor_and_mentee(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -153,7 +154,7 @@ def test_mentorship_session_status__with_status_ignored__with_mentor_and_mentee( @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_completed__duration_equal_to_zero(self, enable_signals): + def test_with_status_completed__duration_equal_to_zero(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -177,7 +178,7 @@ def test_mentorship_session_status__with_status_completed__duration_equal_to_zer @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_completed__duration_equal_to_five_minutes(self, enable_signals): + def test_with_status_completed__duration_equal_to_five_minutes(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -206,7 +207,7 @@ def test_mentorship_session_status__with_status_completed__duration_equal_to_fiv @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_completed__duration_greater_than_five_minutes(self, enable_signals): + def test_with_status_completed__duration_greater_than_five_minutes(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -235,7 +236,7 @@ def test_mentorship_session_status__with_status_completed__duration_greater_than @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) - def test_mentorship_session_status__with_status_completed__with_mentee__with_mentor(self, enable_signals): + def test_with_status_completed__with_mentee__with_mentor(self, enable_signals): enable_signals() from breathecode.feedback.tasks import send_mentorship_session_survey @@ -258,14 +259,45 @@ def test_mentorship_session_status__with_status_completed__with_mentee__with_men self.assertEqual(send_mentorship_session_survey.delay.call_args_list, [call(1)]) self.assertEqual(refund_mentoring_session.delay.call_args_list, []) + """ + 🔽🔽🔽 With status COMPLETED, duration greater than 0:05:00, with mentee, mentor and answer + """ + + @patch("breathecode.feedback.tasks.send_mentorship_session_survey.delay", MagicMock()) + @patch("breathecode.payments.tasks.refund_mentoring_session.delay", MagicMock()) + def test_with_status_completed__with_mentee__with_mentor__with_answer(self, enable_signals): + enable_signals() + + from breathecode.feedback.tasks import send_mentorship_session_survey + + utc_now = timezone.now() + mentorship_session = { + "status": "PENDING", + "started_at": utc_now, + "ended_at": utc_now + timedelta(minutes=5, seconds=1), + } + model = self.bc.database.create(mentorship_session=mentorship_session, user=1, mentorship_service=1, answer=1) + model.mentorship_session.status = "COMPLETED" + model.mentorship_session.save() + + self.assertEqual( + self.bc.database.list_of("mentorship.MentorshipSession"), + [ + self.bc.format.to_dict(model.mentorship_session), + ], + ) + + assert send_mentorship_session_survey.delay.call_args_list == [] + assert refund_mentoring_session.delay.call_args_list == [] + # """ # 🔽🔽🔽 With status COMPLETED, duration greater than 0:05:00, with mentee and with mentor, with service # """ # @patch('breathecode.feedback.tasks.send_mentorship_session_survey.delay', MagicMock()) # @patch('breathecode.payments.tasks.refund_mentoring_session.delay', MagicMock()) - # def test_mentorship_session_status__with_status_completed__with_mentee__with_mentor_and_service(self, enable_signals): - enable_signals() + # def test_with_status_completed__with_mentee__with_mentor_and_service(self, enable_signals): + # enable_signals() # from breathecode.feedback.tasks import send_mentorship_session_survey diff --git a/breathecode/mentorship/tests/urls/tests_academy_session.py b/breathecode/mentorship/tests/urls/tests_academy_session.py index 4d75926fe..d1a2027d4 100644 --- a/breathecode/mentorship/tests/urls/tests_academy_session.py +++ b/breathecode/mentorship/tests/urls/tests_academy_session.py @@ -363,7 +363,6 @@ def test__get__with_two_mentor_profile(self): 🔽🔽🔽 GET with two MentorshipSession, one MentorProfile and one MentorshipService, passing status """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_bad_status(self): statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -403,7 +402,6 @@ def test__get__with_two_mentor_profile__passing_bad_status(self): # teardown self.bc.database.delete("mentorship.MentorshipSession") - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_status(self): statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -466,7 +464,6 @@ def test__get__with_two_mentor_profile__passing_status(self): 🔽🔽🔽 GET with two MentorshipSession, one MentorProfile and one MentorshipService, passing billed """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_billed_as_true__without_mentorship_bill(self): model = self.bc.database.create( user=1, @@ -496,7 +493,6 @@ def test__get__with_two_mentor_profile__passing_billed_as_true__without_mentorsh ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_billed_as_true__with_mentorship_bill(self): model = self.bc.database.create( user=1, @@ -537,7 +533,6 @@ def test__get__with_two_mentor_profile__passing_billed_as_true__with_mentorship_ ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_billed_as_false__with_mentorship_bill(self): model = self.bc.database.create( user=1, @@ -568,7 +563,6 @@ def test__get__with_two_mentor_profile__passing_billed_as_false__with_mentorship ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_billed_as_false__without_mentorship_bill(self): model = self.bc.database.create( user=1, @@ -612,7 +606,6 @@ def test__get__with_two_mentor_profile__passing_billed_as_false__without_mentors 🔽🔽🔽 GET with two MentorshipSession, one MentorProfile and one MentorshipService, passing started_after """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_bad_started_after(self): utc_now = timezone.now() mentorship_session = {"started_at": utc_now} @@ -647,7 +640,6 @@ def test__get__with_two_mentor_profile__passing_bad_started_after(self): ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_started_after(self): utc_now = timezone.now() mentorship_session = {"started_at": utc_now} @@ -692,7 +684,6 @@ def test__get__with_two_mentor_profile__passing_started_after(self): ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_started_after__without_started_at(self): utc_now = timezone.now() model = self.bc.database.create( @@ -742,7 +733,6 @@ def test__get__with_two_mentor_profile__passing_started_after__without_started_a 🔽🔽🔽 GET with two MentorshipSession, one MentorProfile and one MentorshipService, passing ended_before """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_bad_ended_before(self): utc_now = timezone.now() mentorship_session = {"ended_at": utc_now} @@ -777,7 +767,6 @@ def test__get__with_two_mentor_profile__passing_bad_ended_before(self): ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_ended_before(self): utc_now = timezone.now() mentorship_session = {"ended_at": utc_now} @@ -822,7 +811,6 @@ def test__get__with_two_mentor_profile__passing_ended_before(self): ], ) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__get__with_two_mentor_profile__passing_ended_before__without_ended_at(self): utc_now = timezone.now() model = self.bc.database.create( @@ -1503,7 +1491,6 @@ def test__put__without_capabilities(self): 🔽🔽🔽 PUT without id """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__without_id(self): model = self.bc.database.create(user=1, role=1, capability="crud_mentorship_session", profile_academy=1) @@ -1526,7 +1513,6 @@ def test__put__without_id(self): 🔽🔽🔽 PUT not found the MentorshipSession """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__not_found(self): cases = [ (1, {}, False), @@ -1568,7 +1554,6 @@ def test__put__not_found(self): 🔽🔽🔽 PUT found a MentorshipSession, with one MentorProfile and MentorshipService """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__without_required_fields(self): model = self.bc.database.create( user=1, @@ -1600,7 +1585,6 @@ def test__put__found__without_required_fields(self): ) self.assertEqual(self.bc.database.list_of("mentorship.MentorshipBill"), []) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_required_fields(self): model = self.bc.database.create( user=1, @@ -1646,7 +1630,6 @@ def test__put__found__with_required_fields(self): 🔽🔽🔽 PUT with all required fields, is_online is False """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_false(self): mentorship_bill = {"status": random.choice(["RECALCULATE", "DUE"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -1665,8 +1648,6 @@ def test__put__found__with_all_required_fields__is_online_as_false(self): profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -1746,18 +1727,11 @@ def test__put__found__with_all_required_fields__is_online_as_false(self): self.bc.format.to_dict(model.mentorship_bill), ], ) - self.assertEqual( - signals.mentorship_session_status.send_robust.call_args_list, - [ - call(instance=model.mentorship_session, sender=model.mentorship_session.__class__), - ], - ) """ 🔽🔽🔽 PUT with all required fields, is_online is False, MentorshipBill finished """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_false__bill_finished(self): mentorship_bill = {"status": random.choice(["APPROVED", "PAID", "IGNORED"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -1776,8 +1750,6 @@ def test__put__found__with_all_required_fields__is_online_as_false__bill_finishe profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -1826,7 +1798,6 @@ def test__put__found__with_all_required_fields__is_online_as_false__bill_finishe }, ], ) - self.assertEqual(signals.mentorship_session_status.send_robust.call_args_list, []) self.assertEqual( self.bc.database.list_of("mentorship.MentorshipBill"), [ @@ -1838,7 +1809,6 @@ def test__put__found__with_all_required_fields__is_online_as_false__bill_finishe 🔽🔽🔽 PUT passing a MentorshipBill with some MentorshipSession without MentorshipService """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__passing_a_bill_with_some_session_without_service(self): mentorship_bill = {"status": "DUE"} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -1868,8 +1838,6 @@ def test__put__found__passing_a_bill_with_some_session_without_service(self): profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -1949,9 +1917,3 @@ def test__put__found__passing_a_bill_with_some_session_without_service(self): }, ], ) - self.assertEqual( - signals.mentorship_session_status.send_robust.call_args_list, - [ - call(instance=model.mentorship_session[0], sender=model.mentorship_session[0].__class__), - ], - ) diff --git a/breathecode/mentorship/tests/urls/tests_academy_session_id.py b/breathecode/mentorship/tests/urls/tests_academy_session_id.py index 181a3c7b4..c264ee741 100644 --- a/breathecode/mentorship/tests/urls/tests_academy_session_id.py +++ b/breathecode/mentorship/tests/urls/tests_academy_session_id.py @@ -241,7 +241,6 @@ def test__put__without_capabilities(self): 🔽🔽🔽 PUT not found the MentorshipSession """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__not_found(self): cases = [ (1, {}, False), @@ -282,7 +281,6 @@ def test__put__not_found(self): 🔽🔽🔽 PUT found a MentorshipSession, with one MentorProfile and MentorshipService """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__without_required_fields(self): model = self.bc.database.create( user=1, @@ -313,7 +311,6 @@ def test__put__found__without_required_fields(self): ) self.assertEqual(self.bc.database.list_of("mentorship.MentorshipBill"), []) - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_required_fields(self): model = self.bc.database.create( user=1, @@ -356,7 +353,6 @@ def test__put__found__with_required_fields(self): 🔽🔽🔽 PUT with all required fields, is_online is False """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_false(self): mentorship_bill = {"status": random.choice(["RECALCULATE", "DUE"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -375,8 +371,6 @@ def test__put__found__with_all_required_fields__is_online_as_false(self): profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -450,18 +444,11 @@ def test__put__found__with_all_required_fields__is_online_as_false(self): self.bc.format.to_dict(model.mentorship_bill), ], ) - self.assertEqual( - signals.mentorship_session_status.send_robust.call_args_list, - [ - call(instance=model.mentorship_session, sender=model.mentorship_session.__class__), - ], - ) """ 🔽🔽🔽 PUT with all required fields, is_online is False, MentorshipBill finished """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_false__bill_finished(self): mentorship_bill = {"status": random.choice(["APPROVED", "PAID", "IGNORED"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -480,8 +467,6 @@ def test__put__found__with_all_required_fields__is_online_as_false__bill_finishe profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -533,13 +518,11 @@ def test__put__found__with_all_required_fields__is_online_as_false__bill_finishe self.bc.format.to_dict(model.mentorship_bill), ], ) - self.assertEqual(str(signals.mentorship_session_status.send_robust.call_args_list), str([])) """ 🔽🔽🔽 PUT with all required fields, is_online is True, trying to edit readonly fields """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_true__trying_to_edit_readonly_fields(self): statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -554,8 +537,6 @@ def test__put__found__with_all_required_fields__is_online_as_true__trying_to_edi profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -620,13 +601,11 @@ def test__put__found__with_all_required_fields__is_online_as_true__trying_to_edi self.bc.format.to_dict(model.mentorship_bill), ], ) - self.assertEqual(str(signals.mentorship_session_status.send_robust.call_args_list), str([])) """ 🔽🔽🔽 PUT with all required fields, is_online is True """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_true(self): mentorship_bill = {"status": random.choice(["RECALCULATE", "DUE"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -645,8 +624,6 @@ def test__put__found__with_all_required_fields__is_online_as_true(self): profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -712,18 +689,10 @@ def test__put__found__with_all_required_fields__is_online_as_true(self): ], ) - self.assertEqual( - signals.mentorship_session_status.send_robust.call_args_list, - [ - call(instance=model.mentorship_session, sender=model.mentorship_session.__class__), - ], - ) - """ 🔽🔽🔽 PUT with all required fields, is_online is True, MentorshipBill finished """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) def test__put__found__with_all_required_fields__is_online_as_true__bill_finished(self): mentorship_bill = {"status": random.choice(["APPROVED", "PAID", "IGNORED"])} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -739,8 +708,6 @@ def test__put__found__with_all_required_fields__is_online_as_true__bill_finished profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -792,15 +759,11 @@ def test__put__found__with_all_required_fields__is_online_as_true__bill_finished self.bc.format.to_dict(model.mentorship_bill), ], ) - self.assertEqual(str(signals.mentorship_session_status.send_robust.call_args_list), str([])) """ 🔽🔽🔽 PUT passing a MentorshipBill with some MentorshipSession without MentorshipService """ - @patch("breathecode.mentorship.signals.mentorship_session_status.send_robust", MagicMock()) - # @patch('breathecode.mentorship.signals.mentorship_session_status.send_robust', - # MagicMock(side_effect=[None, None, Exception('error')])) def test__put__found__passing_a_bill_with_some_session_without_service(self): mentorship_bill = {"status": "DUE"} statuses = ["PENDING", "STARTED", "COMPLETED", "FAILED", "IGNORED"] @@ -830,8 +793,6 @@ def test__put__found__passing_a_bill_with_some_session_without_service(self): profile_academy=1, ) - signals.mentorship_session_status.send_robust.call_args_list = [] - self.bc.request.set_headers(academy=1) self.client.force_authenticate(model.user) @@ -906,10 +867,3 @@ def test__put__found__passing_a_bill_with_some_session_without_service(self): }, ], ) - - self.assertEqual( - signals.mentorship_session_status.send_robust.call_args_list, - [ - call(instance=model.mentorship_session[0], sender=model.mentorship_session[0].__class__), - ], - ) diff --git a/breathecode/monitoring/signals.py b/breathecode/monitoring/signals.py index cbf244ef7..32917a7a2 100644 --- a/breathecode/monitoring/signals.py +++ b/breathecode/monitoring/signals.py @@ -1,9 +1,8 @@ -""" -For each signal you want other apps to be able to receive, you have to -declare a new variable here like this: -""" +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -from django import dispatch +from task_manager.django.dispatch import Emisor -github_webhook = dispatch.Signal() -stripe_webhook = dispatch.Signal() +emisor = Emisor("breathecode.monitoring") + +github_webhook = emisor.signal("github_webhook") +stripe_webhook = emisor.signal("stripe_webhook") diff --git a/breathecode/notify/utils/hook_manager.py b/breathecode/notify/utils/hook_manager.py index a47098a9a..f274edd6d 100644 --- a/breathecode/notify/utils/hook_manager.py +++ b/breathecode/notify/utils/hook_manager.py @@ -211,16 +211,17 @@ def serialize(self, payload: dict | list | tuple) -> dict | list | tuple: return payload - for key in payload: - if isinstance(payload[key], dict): - self.serialize(payload[key]) + if isinstance(payload, dict): + for key in payload: + if isinstance(payload[key], dict): + self.serialize(payload[key]) - elif isinstance(payload[key], (list, tuple)): - for item in payload[key]: - self.serialize(item) + elif isinstance(payload[key], (list, tuple)): + for item in payload[key]: + self.serialize(item) - elif isinstance(payload[key], timedelta): - payload[key] = payload[key].total_seconds() + elif isinstance(payload[key], timedelta): + payload[key] = payload[key].total_seconds() return payload diff --git a/breathecode/registry/signals.py b/breathecode/registry/signals.py index d49fb1116..cbb8d3066 100644 --- a/breathecode/registry/signals.py +++ b/breathecode/registry/signals.py @@ -1,11 +1,10 @@ -""" -For each signal you want other apps to be able to receive, you have to -declare a new variable here like this: -""" +"""For each signal you want other apps to be able to receive, you have to declare a new variable here like this:""" -from django import dispatch +from task_manager.django.dispatch import Emisor -asset_slug_modified = dispatch.Signal() -asset_readme_modified = dispatch.Signal() -asset_title_modified = dispatch.Signal() -asset_status_updated = dispatch.Signal() +emisor = Emisor("breathecode.registry") + +asset_slug_modified = emisor.signal("asset_slug_modified") +asset_readme_modified = emisor.signal("asset_readme_modified") +asset_title_modified = emisor.signal("asset_title_modified") +asset_status_updated = emisor.signal("asset_status_updated") From a18fce57d715ab1c101955e49fd4364b3a71e578 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 16 Jul 2024 11:52:31 -0500 Subject: [PATCH 2/2] add consumer field to payment services --- .../urls/tests_me_task_id_coderevision.py | 12 ++--- .../tests/urls/tests_me_event_id_join.py | 36 ++++++------- .../tests_me_event_liveclass_join_hash.py | 22 ++++---- .../tests_meet_slug_service_slug.py | 54 +++++++++---------- .../migrations/0051_service_consumer.py | 30 +++++++++++ breathecode/payments/models.py | 14 ++++- breathecode/provisioning/actions.py | 19 +++++-- breathecode/provisioning/models.py | 2 +- breathecode/provisioning/tasks.py | 27 ++++++++++ .../tests/urls/v2/tests_academy_asset_slug.py | 8 +-- breathecode/registry/views.py | 2 +- .../utils/tests/decorators/tests_consume.py | 53 +++++++++--------- 12 files changed, 178 insertions(+), 101 deletions(-) create mode 100644 breathecode/payments/migrations/0051_service_consumer.py diff --git a/breathecode/assignments/tests/urls/tests_me_task_id_coderevision.py b/breathecode/assignments/tests/urls/tests_me_task_id_coderevision.py index dfd935093..a1404c7a0 100644 --- a/breathecode/assignments/tests/urls/tests_me_task_id_coderevision.py +++ b/breathecode/assignments/tests/urls/tests_me_task_id_coderevision.py @@ -252,7 +252,7 @@ def test__post__no_consumables(bc: Breathecode, client: APIClient, patch_post): }, service={ "type": "VOID", - "slug": "add_code_review", + "consumer": "ADD_CODE_REVIEW", }, ) client.force_authenticate(model.user) @@ -290,7 +290,7 @@ def test__post__no_tasks(bc: Breathecode, client: APIClient, patch_post): app={"slug": "rigobot", "app_url": bc.fake.url()}, service={ "type": "VOID", - "slug": "add_code_review", + "consumer": "ADD_CODE_REVIEW", }, ) client.force_authenticate(model.user) @@ -333,7 +333,7 @@ def test__post__no_github_accounts(bc: Breathecode, client: APIClient, patch_pos }, service={ "type": "VOID", - "slug": "add_code_review", + "consumer": "ADD_CODE_REVIEW", }, ) client.force_authenticate(model.user) @@ -378,7 +378,7 @@ def test__post__auth(bc: Breathecode, client: APIClient, patch_post): consumable=1, service={ "type": "VOID", - "slug": "add_code_review", + "consumer": "ADD_CODE_REVIEW", }, ) client.force_authenticate(model.user) @@ -474,7 +474,7 @@ def test__post__auth__no_saas__finantial_status_no_late( consumable=1, service={ "type": "VOID", - "slug": "add_code_review", + "consumer": "ADD_CODE_REVIEW", }, academy=academy, cohort=cohort, @@ -548,7 +548,7 @@ def test__post__auth__no_saas__finantial_status_late(bc: Breathecode, client: AP consumable=1, service={ "type": "VOID", - "slug": "add_code_review", + "slug": "ADD_CODE_REVIEW", }, cohort_user=cohort_user, cohort=cohort, diff --git a/breathecode/events/tests/urls/tests_me_event_id_join.py b/breathecode/events/tests/urls/tests_me_event_id_join.py index 6f5afe515..a3254edf0 100644 --- a/breathecode/events/tests/urls/tests_me_event_id_join.py +++ b/breathecode/events/tests/urls/tests_me_event_id_join.py @@ -179,7 +179,7 @@ def test_no_consumables(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__live_event_not_found(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} model = self.bc.database.create(user=1, service=service, token=1) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -216,7 +216,7 @@ def test_no_consumables__bypass_with_feature_flag__live_event_not_found(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__with_live_event__cohort_without_url(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} delta = timedelta(seconds=random.randint(1, 1000)) event = {"starting_at": UTC_NOW - delta, "ending_at": UTC_NOW + delta} event_type = {"icon_url": self.bc.fake.url()} @@ -274,7 +274,7 @@ def test_no_consumables__bypass_with_feature_flag__with_live_event__cohort_witho @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__with_live_event__cohort_with_url(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -344,7 +344,7 @@ def test_no_consumables__bypass_with_feature_flag__with_live_event__cohort_with_ @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__live_event_not_found(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} model = self.bc.database.create(user=1, service=service, token=1) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -381,7 +381,7 @@ def test_no_consumables__it_try_to_consume__live_event_not_found(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__with_live_event__cohort_without_url(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} delta = timedelta(seconds=random.randint(1, 1000)) event = {"starting_at": UTC_NOW - delta, "ending_at": UTC_NOW + delta} event_type = {"icon_url": self.bc.fake.url()} @@ -439,7 +439,7 @@ def test_no_consumables__it_try_to_consume__with_live_event__cohort_without_url( @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__with_live_event__cohort_with_url(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -529,7 +529,7 @@ def test_no_consumables__it_try_to_consume__with_live_event__cohort_with_url(sel @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_past(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -605,7 +605,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_past(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_future(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -713,7 +713,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_future(self @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__academy_no_saas__non_free1(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -825,7 +825,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__aca @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__academy_no_saas__non_free2(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -939,7 +939,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__aca @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_is_free_with_cohort_users_saas__cohort(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) @@ -1050,7 +1050,7 @@ def test_is_free_with_cohort_users_saas__cohort(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_is_free_with_cohort_users_saas__academy(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1160,7 +1160,7 @@ def test_is_free_with_cohort_users_saas__academy(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_is_free_with_cohort_users_no_saas__cohort(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1255,7 +1255,7 @@ def test_is_free_with_cohort_users_no_saas__cohort(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_is_free_with_cohort_users_no_saas__academy(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1346,7 +1346,7 @@ def test_is_free_with_cohort_users_no_saas__academy(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__show_countdown(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1447,7 +1447,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__sho @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__show_countdown(self): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = self.bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1559,7 +1559,7 @@ def test_with_consumable__it_try_to_consume__with_live_event__in_the_future__sho def test__post__auth__no_saas__finantial_status_no_late( bc: Breathecode, client: fx.Client, academy, cohort, cohort_user ): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { @@ -1644,7 +1644,7 @@ def test__post__auth__no_saas__finantial_status_no_late( @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test__post__auth__no_saas__finantial_status_late(bc: Breathecode, client: fx.Client, academy, cohort): - service = {"slug": "event_join"} + service = {"consumer": "EVENT_JOIN"} online_meeting_url = bc.fake.url() delta = timedelta(seconds=random.randint(1, 1000)) event = { diff --git a/breathecode/events/tests/urls/tests_me_event_liveclass_join_hash.py b/breathecode/events/tests/urls/tests_me_event_liveclass_join_hash.py index 7f764bde1..b8279b951 100644 --- a/breathecode/events/tests/urls/tests_me_event_liveclass_join_hash.py +++ b/breathecode/events/tests/urls/tests_me_event_liveclass_join_hash.py @@ -154,7 +154,7 @@ def test_no_consumables(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__live_class_not_found(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} model = self.bc.database.create(user=1, group=1, service=service, token=1) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -190,7 +190,7 @@ def test_no_consumables__bypass_with_feature_flag__live_class_not_found(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__with_live_class__cohort_without_url(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} delta = timedelta(seconds=random.randint(1, 1000)) live_class = {"starting_at": UTC_NOW - delta, "ending_at": UTC_NOW + delta} model = self.bc.database.create(user=1, group=1, service=service, live_class=live_class, cohort_user=1, token=1) @@ -236,7 +236,7 @@ def test_no_consumables__bypass_with_feature_flag__with_live_class__cohort_witho @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__bypass_with_feature_flag__with_live_class__cohort_with_url(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url} delta = timedelta(seconds=random.randint(1, 1000)) @@ -287,7 +287,7 @@ def test_no_consumables__bypass_with_feature_flag__with_live_class__cohort_with_ @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__live_class_not_found(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} model = self.bc.database.create(user=1, group=1, service=service, token=1) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -323,7 +323,7 @@ def test_no_consumables__it_try_to_consume__live_class_not_found(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__with_live_class__cohort_without_url(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} delta = timedelta(seconds=random.randint(1, 1000)) live_class = {"starting_at": UTC_NOW - delta, "ending_at": UTC_NOW + delta} model = self.bc.database.create( @@ -371,7 +371,7 @@ def test_no_consumables__it_try_to_consume__with_live_class__cohort_without_url( @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_no_consumables__it_try_to_consume__with_live_class__cohort_with_url(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url, "available_as_saas": True} delta = timedelta(seconds=random.randint(1, 1000)) @@ -425,7 +425,7 @@ def test_no_consumables__it_try_to_consume__with_live_class__cohort_with_url(sel @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_class__in_the_past(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url} delta = timedelta(seconds=random.randint(1, 1000)) @@ -481,7 +481,7 @@ def test_with_consumable__it_try_to_consume__with_live_class__in_the_past(self): @patch("breathecode.admissions.signals.student_edu_status_updated.send_robust", MagicMock(return_value=None)) @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_class__in_the_future(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url, "available_as_saas": True} delta = timedelta(seconds=random.randint(1, 1000)) @@ -571,7 +571,7 @@ def test_with_consumable__it_try_to_consume__with_live_class__in_the_future(self @patch("breathecode.payments.tasks.end_the_consumption_session.apply_async", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_class__in_the_future__show_countdown(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url, "available_as_saas": True} delta = timedelta(seconds=random.randint(1, 1000)) @@ -661,7 +661,7 @@ def test_with_consumable__it_try_to_consume__with_live_class__in_the_future__sho @patch("breathecode.events.tasks.mark_live_class_as_started.delay", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_class__on_starting_time(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url, "available_as_saas": True} delta = timedelta(seconds=random.randint(1, 1000)) @@ -741,7 +741,7 @@ def test_with_consumable__it_try_to_consume__with_live_class__on_starting_time(s @patch("breathecode.events.tasks.mark_live_class_as_started.delay", MagicMock(return_value=None)) def test_with_consumable__it_try_to_consume__with_live_class__on_starting_time_is_teacher(self): - service = {"slug": "live_class_join"} + service = {"consumer": "LIVE_CLASS_JOIN"} online_meeting_url = self.bc.fake.url() cohort = {"online_meeting_url": online_meeting_url, "available_as_saas": True} delta = timedelta(seconds=random.randint(1, 1000)) diff --git a/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py b/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py index 939901c0e..713f6dc2e 100644 --- a/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py +++ b/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py @@ -481,7 +481,7 @@ def test_without_auth(self): """ def test_without_mentor_profile(self): - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create(user=1, token=1, service=service) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -516,7 +516,7 @@ def test_without_mentor_profile(self): def test_no_mentorship_service(self): slug = self.bc.fake.slug() - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create(user=1, token=1, mentor_profile=1, service=service) querystring = self.bc.format.to_querystring({"token": model.token.key}) @@ -559,7 +559,7 @@ def test_no_mentorship_service(self): """ def test_with_mentor_profile(self): - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create( user=1, token=1, @@ -614,7 +614,7 @@ def test_with_mentor_profile__bad_statuses(self): cases = [{"status": x} for x in ["INVITED", "INNACTIVE"]] for mentor_profile in cases: - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create( user=1, token=1, @@ -675,7 +675,7 @@ def test_with_mentor_profile__good_statuses__without_mentor_urls(self): cases = [{"status": x} for x in ["ACTIVE", "UNLISTED"]] for mentor_profile in cases: - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create( user=1, token=1, @@ -769,7 +769,7 @@ def test_with_mentor_profile__good_statuses__with_mentor_urls__with_mentee(self) ] for mentor_profile in cases: - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create( user=1, token=1, @@ -865,7 +865,7 @@ def test_with_mentor_profile__good_statuses__with_mentor_urls__with_mentee__not_ id += 1 mentor_profile = {**args, "user_id": 1} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} model = self.bc.database.create( user=1, token=1, @@ -961,7 +961,7 @@ def test_with_mentor_profile__good_statuses__with_mentor_urls__session_without_m for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=1, token=1, service=service) id = 0 @@ -1062,7 +1062,7 @@ def test_with_mentor_profile__good_statuses__with_mentor_urls__session_without__ for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=1, token=1, service=service) id = 0 @@ -1188,7 +1188,7 @@ def test_with_mentor_profile__good_statuses__with_mentor_urls__session_without__ ] for mentorship_session in session_cases: - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=1, token=1, service=service) model = self.bc.database.create( @@ -1301,7 +1301,7 @@ def test_with_mentor_profile__passing_session__passing_mentee__passing_redirect( for mentor_profile in cases: id += 1 - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=1, token=1, service=service) mentorship_session = {"mentee_id": None} @@ -1405,7 +1405,7 @@ def test_with_mentor_profile__without_user_name(self): } for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} id = 0 for mentor_profile in cases: @@ -1706,7 +1706,7 @@ def test_with_mentor_profile__academy_available_as_saas__flag_eq_true__mentee_wi } for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} id = 0 for mentor_profile in cases: @@ -1815,7 +1815,7 @@ def test_with_mentor_profile__academy_available_as_saas__flag_eq_true__mentee_wi } for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} id = 0 for mentor_profile in cases: @@ -1931,7 +1931,7 @@ def test_with_mentor_profile__academy_available_as_saas__flag_eq_true__mentee_wi } for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} id = 0 for mentor_profile in cases: @@ -2090,7 +2090,7 @@ def test_with_mentor_profile__academy_available_as_saas__flag_eq_true__bypass_me } for x in ["ACTIVE", "UNLISTED"] ] - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} id = 0 for mentor_profile in cases: @@ -2208,7 +2208,7 @@ def test_with_mentor_profile__ends_at_less_now(self): id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=10) @@ -2323,7 +2323,7 @@ def test_with_mentor_profile__ends_at_less_now__with_extend_true(self): id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=10) @@ -2474,7 +2474,7 @@ def test_with_mentor_profile__ends_at_less_now__with_extend_true__extend_session id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=10) @@ -2623,7 +2623,7 @@ def test_with_mentor_profile__ends_at_less_now__with_extend_true__session_can_no id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=10) @@ -2772,7 +2772,7 @@ def test_with_mentor_profile__ends_at_less_now__with_extend_true__redirect_to_se id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=3600 / 2 + 1) @@ -2921,7 +2921,7 @@ def test_with_mentor_profile__redirect_to_session__no_saas(self): id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=3600 / 2 + 1) @@ -3053,7 +3053,7 @@ def test_with_mentor_profile__redirect_to_session__saas(self): id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=3600 / 2 + 1) @@ -3170,7 +3170,7 @@ def test_with_mentor_profile__redirect_to_session__saas__(self): id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} academy = {"available_as_saas": True} how_many = random.randint(1, 100) consumable = {"how_many": how_many} @@ -3358,7 +3358,7 @@ def test_get_pending_sessions_or_create_returns_empty_queryset(self): ] for user, name in cases: - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = self.bc.database.create(user=user, token=1, service=service) ends_at = UTC_NOW - timedelta(seconds=10) @@ -3513,7 +3513,7 @@ def test__post__auth__no_saas__finantial_status_no_late( id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} base = bc.database.create( user=user, token=1, service=service, academy=academy, cohort=cohort, cohort_user=cohort_user ) @@ -3659,7 +3659,7 @@ def test__post__auth__no_saas__finantial_status_late(bc: Breathecode, client: fx id += 1 user = {"first_name": "", "last_name": ""} - service = {"slug": "join_mentorship"} + service = {"consumer": "JOIN_MENTORSHIP"} cohort_user = {"finantial_status": "LATE", "educational_status": "ACTIVE"} base = bc.database.create( user=user, token=1, service=service, academy=academy, cohort=cohort, cohort_user=cohort_user diff --git a/breathecode/payments/migrations/0051_service_consumer.py b/breathecode/payments/migrations/0051_service_consumer.py new file mode 100644 index 000000000..d70e58082 --- /dev/null +++ b/breathecode/payments/migrations/0051_service_consumer.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.6 on 2024-07-16 16:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("payments", "0050_planfinancing_conversion_info_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="service", + name="consumer", + field=models.CharField( + choices=[ + ("ADD_CODE_REVIEW", "Add code review"), + ("LIVE_CLASS_JOIN", "Live class join"), + ("EVENT_JOIN", "Event join"), + ("JOIN_MENTORSHIP", "Join mentorship"), + ("READ_LESSON", "Read lesson"), + ("NO_SET", "No set"), + ], + default="NO_SET", + help_text="Service type", + max_length=15, + ), + ), + ] diff --git a/breathecode/payments/models.py b/breathecode/payments/models.py index 1ef9d8f5e..6af7233b1 100644 --- a/breathecode/payments/models.py +++ b/breathecode/payments/models.py @@ -161,6 +161,14 @@ class Type(models.TextChoices): EVENT_TYPE_SET = ("EVENT_TYPE_SET", "Event type set") VOID = ("VOID", "Void") + class Consumer(models.TextChoices): + ADD_CODE_REVIEW = ("ADD_CODE_REVIEW", "Add code review") + LIVE_CLASS_JOIN = ("LIVE_CLASS_JOIN", "Live class join") + EVENT_JOIN = ("EVENT_JOIN", "Event join") + JOIN_MENTORSHIP = ("JOIN_MENTORSHIP", "Join mentorship") + READ_LESSON = ("READ_LESSON", "Read lesson") + NO_SET = ("NO_SET", "No set") + groups = models.ManyToManyField( Group, blank=True, help_text="Groups that can access the customer that bought this service" ) @@ -169,6 +177,7 @@ class Type(models.TextChoices): default=None, null=True, blank=True, help_text="Session duration, used in consumption sessions" ) type = models.CharField(max_length=22, choices=Type, default=Type.COHORT_SET, help_text="Service type") + consumer = models.CharField(max_length=15, choices=Consumer, default=Consumer.NO_SET, help_text="Service type") def __str__(self): return self.slug @@ -1301,7 +1310,10 @@ def list( # Service if service and isinstance(service, str) and not service.isdigit(): - param["service_item__service__slug"] = service + if "_" in service: + param["service_item__service__consumer"] = service.upper() + else: + param["service_item__service__slug"] = service elif service and isinstance(service, str) and service.isdigit(): param["service_item__service__id"] = int(service) diff --git a/breathecode/provisioning/actions.py b/breathecode/provisioning/actions.py index 4175d724f..aa06d12aa 100644 --- a/breathecode/provisioning/actions.py +++ b/breathecode/provisioning/actions.py @@ -217,7 +217,13 @@ class ActivityContext(TypedDict): profile_academies: dict[str, QuerySet[ProfileAcademy]] -def handle_pending_github_user(organization: str, username: str, starts: Optional[datetime] = None) -> list[Academy]: +def is_valid_string(value): + return isinstance(value, str) and value.strip() != "" + + +def handle_pending_github_user( + organization: Optional[str], username: str, starts: Optional[datetime] = None +) -> list[Academy]: orgs = AcademyAuthSettings.objects.filter(github_username__iexact=organization) orgs = [ x @@ -238,8 +244,11 @@ def handle_pending_github_user(organization: str, username: str, starts: Optiona user = None credentials = None - if username: - credentials = CredentialsGithub.objects.filter(username__iexact=username).first() + if is_valid_string(username): + credentials = CredentialsGithub.objects.filter(username__isnull=False, username__iexact=username).first() + + else: + logger.error(f"Username is invalid, cannot find github credentials for username {username}") if credentials: user = credentials.user @@ -610,8 +619,8 @@ def add_rigobot_activity(context: ActivityContext, field: dict, position: int) - logger.error(f'User {field["user_id"]} not found') return - if field["billing_status"] != "OPEN": - return + # if field["billing_status"] != "OPEN": + # return github_academy_user_log = context["github_academy_user_logs"].get(user.id, None) date = datetime.fromisoformat(field["consumption_period_start"]) diff --git a/breathecode/provisioning/models.py b/breathecode/provisioning/models.py index 844aec758..f8cfc91c6 100644 --- a/breathecode/provisioning/models.py +++ b/breathecode/provisioning/models.py @@ -198,7 +198,7 @@ class ProvisioningConsumptionEvent(models.Model): ) def __str__(self): - return str(self.quantity) + " - " + self.task_associated_slug + return f"{self.quantity} - {self.task_associated_slug}" class ProvisioningUserConsumption(models.Model): diff --git a/breathecode/provisioning/tasks.py b/breathecode/provisioning/tasks.py index 90b15ff06..17d24876f 100644 --- a/breathecode/provisioning/tasks.py +++ b/breathecode/provisioning/tasks.py @@ -80,6 +80,29 @@ def calculate_bill_amounts(hash: str, *, force: bool = False, **_: Any): "Owner", ] + elif bills[0].vendor.name == "Rigobot": + fields = [ + "organization", + "consumption_period_id", + "consumption_period_start", + "consumption_period_end", + "billing_status", + "total_spent_period", + "consumption_item_id", + "user_id", + "email", + "consumption_type", + "pricing_type", + "total_spent", + "total_tokens", + "model", + "purpose_id", + "purpose_slug", + "purpose", + "created_at", + "github_username", + ] + storage = Storage() cloud_file = storage.file(os.getenv("PROVISIONING_BUCKET", None), hash) if not cloud_file.exists(): @@ -107,6 +130,10 @@ def calculate_bill_amounts(hash: str, *, force: bool = False, **_: Any): first = df1["Date"][0].split("-") last = df2["Date"][0].split("-") + elif bills[0].vendor.name == "Rigobot": + first = df1["consumption_period_start"][0].split("-") + last = df2["consumption_period_start"][0].split("-") + first[2] = first[2].split("T")[0] last[2] = last[2].split("T")[0] diff --git a/breathecode/registry/tests/urls/v2/tests_academy_asset_slug.py b/breathecode/registry/tests/urls/v2/tests_academy_asset_slug.py index 59ebc2e4e..bbf0df086 100644 --- a/breathecode/registry/tests/urls/v2/tests_academy_asset_slug.py +++ b/breathecode/registry/tests/urls/v2/tests_academy_asset_slug.py @@ -133,7 +133,7 @@ def test_no_consumables(bc: Breathecode, client: APIClient): def test_no_asset(bc: Breathecode, client: APIClient): """Test /certificate without auth""" model = bc.database.create( - user=1, profile_academy=1, role=1, capability="read_asset", service={"slug": "read-lesson"}, consumable=1 + user=1, profile_academy=1, role=1, capability="read_asset", service={"consumer": "READ_LESSON"}, consumable=1 ) client.force_authenticate(user=model.user) url = reverse_lazy("v2:registry:academy_asset_slug", kwargs={"asset_slug": "model_slug"}) @@ -157,7 +157,7 @@ def test_with_asset(bc: Breathecode, client: APIClient): profile_academy=1, role=1, capability="read_asset", - service={"slug": "read-lesson"}, + service={"consumer": "READ_LESSON"}, consumable=1, asset=1, asset_category=1, @@ -221,7 +221,7 @@ def test_with_asset__no_saas__finantial_status_no_late( profile_academy=1, role=1, capability="read_asset", - service={"slug": "read-lesson"}, + service={"consumer": "READ_LESSON"}, consumable=1, asset=1, asset_category=1, @@ -266,7 +266,7 @@ def test_with_asset__no_saas__finantial_status_late(bc: Breathecode, client: API profile_academy=1, role=1, capability="read_asset", - service={"slug": "read-lesson"}, + service={"consumer": "READ_LESSON"}, consumable=1, asset={"slug": slug}, syllabus_version={ diff --git a/breathecode/registry/views.py b/breathecode/registry/views.py index a46b7de5b..f7c04622b 100644 --- a/breathecode/registry/views.py +++ b/breathecode/registry/views.py @@ -1192,7 +1192,7 @@ class V2AcademyAssetView(APIView): extensions = APIViewExtensions(cache=AssetCache, sort="-published_at", paginate=True) @capable_of("read_asset") - @consume("read-lesson", consumer=asset_by_slug) + @consume("read_lesson", consumer=asset_by_slug) def get(self, request, asset: Asset, academy: Academy): serializer = AcademyAssetSerializer(asset) return Response(serializer.data) diff --git a/breathecode/utils/tests/decorators/tests_consume.py b/breathecode/utils/tests/decorators/tests_consume.py index f8b7ac288..e9e7b03f1 100644 --- a/breathecode/utils/tests/decorators/tests_consume.py +++ b/breathecode/utils/tests/decorators/tests_consume.py @@ -22,8 +22,7 @@ from breathecode.utils.decorators import ServiceContext from capyc.rest_framework import pytest as capy -SERVICE = "can_kill_kenny" -PERMISSION = "can_kill_kenny" +SERVICE = random.choice([value for value, _ in models.Service.Consumer.choices]) UTC_NOW = timezone.now() @@ -43,10 +42,10 @@ def setup(db, monkeypatch: pytest.MonkeyPatch) -> None: def consumer(context: ServiceContext, args: tuple, kwargs: dict) -> tuple[dict, tuple, dict]: # remember the objects are passed by reference, so you need to clone them to avoid modify the object # receive by the mock causing side effects - args = (*args, PERMISSION) + args = (*args, SERVICE) kwargs = { **kwargs, - "permission": PERMISSION, + "permission": SERVICE, } context = { **context, @@ -64,10 +63,10 @@ def consumer(context: ServiceContext, args: tuple, kwargs: dict) -> tuple[dict, def consumer_with_time_of_life(context: ServiceContext, args: tuple, kwargs: dict) -> tuple[dict, tuple, dict]: # remember the objects are passed by reference, so you need to clone them to avoid modify the object # receive by the mock causing side effects - args = (*args, PERMISSION) + args = (*args, SERVICE) kwargs = { **kwargs, - "permission": PERMISSION, + "permission": SERVICE, } context = { **context, @@ -346,7 +345,7 @@ async def test_with_user__with_group_related_to_permission__without_consumable( self, bc: Breathecode, make_view_all_cases ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] model = await bc.database.acreate(user=user, service=services, service_item={"service_id": 2}) view, _, _, _ = await make_view_all_cases(user=model.user, decorator_params={}, url_params={}) @@ -372,7 +371,7 @@ async def test_with_user__with_group_related_to_permission__consumable__how_many self, bc: Breathecode, make_view_all_cases ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": -1} model = await bc.database.acreate( @@ -403,7 +402,7 @@ async def test_with_user__with_group_related_to_permission__consumable__how_many self, bc: Breathecode, make_view_all_cases ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": 0} model = await bc.database.acreate( @@ -433,7 +432,7 @@ async def test_with_user__with_group_related_to_permission__consumable__how_many self, bc: Breathecode, make_view_all_cases ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": random.randint(1, 100)} model = await bc.database.acreate( @@ -464,7 +463,7 @@ async def test_with_user__with_group_related_to_permission__group_was_blackliste self, bc: Breathecode, make_view_all_cases ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] group = {"permission_id": 2, "name": "secret"} consumable = {"how_many": 1} model = await bc.database.acreate( @@ -562,7 +561,7 @@ async def test__function__get__with_user__with_group_related_to_permission__with self, bc: Breathecode, make_view_consumer_cases, partial_equality ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] model = await bc.database.acreate(user=user, service=services, service_item={"service_id": 2}) view, _, based_class, _ = await make_view_consumer_cases(user=model.user, decorator_params={}, url_params={}) @@ -586,7 +585,7 @@ async def test__function__get__with_user__with_group_related_to_permission__with { "utc_now": UTC_NOW, "consumer": CONSUMER_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -616,7 +615,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons self, bc: Breathecode, make_view_consumer_cases, partial_equality ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": -1} model = await bc.database.acreate( @@ -645,7 +644,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons { "utc_now": UTC_NOW, "consumer": CONSUMER_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -677,7 +676,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons self, bc: Breathecode, make_view_consumer_cases, partial_equality ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": 0} model = await bc.database.acreate( @@ -707,7 +706,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons { "utc_now": UTC_NOW, "consumer": CONSUMER_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -737,7 +736,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons self, bc: Breathecode, make_view_consumer_cases, partial_equality ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": random.randint(1, 100)} model = await bc.database.acreate( @@ -766,7 +765,7 @@ async def test__function__get__with_user__with_group_related_to_permission__cons { "utc_now": UTC_NOW, "consumer": CONSUMER_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -798,7 +797,7 @@ async def test__function__get__with_user__with_group_related_to_permission__grou self, bc: Breathecode, make_view_consumer_cases, partial_equality ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] group = {"permission_id": 2, "name": "secret"} consumable = {"how_many": 1} model = await bc.database.acreate( @@ -828,7 +827,7 @@ async def test__function__get__with_user__with_group_related_to_permission__grou { "utc_now": UTC_NOW, "consumer": CONSUMER_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -859,7 +858,7 @@ async def test__function__get__with_user__without_consumption_session( ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": 1} model = await bc.database.acreate( @@ -888,7 +887,7 @@ async def test__function__get__with_user__without_consumption_session( { "utc_now": UTC_NOW, "consumer": CONSUMER_WITH_TIME_OF_LIFE_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -924,7 +923,7 @@ async def test__with_user__consumption_session__does_not_match( ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": 1} model = await bc.database.acreate( @@ -953,7 +952,7 @@ async def test__with_user__consumption_session__does_not_match( { "utc_now": UTC_NOW, "consumer": CONSUMER_WITH_TIME_OF_LIFE_MOCK, - "permission": PERMISSION, + "permission": SERVICE, "consumables": consumables, }, ) @@ -988,7 +987,7 @@ async def test__with_user__consumption_session__does_not_match__consumables_minu ): user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] n = random.randint(1, 4) consumable = {"how_many": n} @@ -1037,7 +1036,7 @@ async def test__with_user__consumption_session__match(self, bc: Breathecode, mak CONSUMER_WITH_TIME_OF_LIFE_MOCK.call_args_list = [] user = {"user_permissions": []} - services = [{}, {"slug": PERMISSION}] + services = [{}, {"consumer": SERVICE.upper()}] consumable = {"how_many": 1} consumption_session = {