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/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/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/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/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/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/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/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 9c00508ab..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
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/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")
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 = {