diff --git a/breathecode/events/migrations/0062_event_recording_url.py b/breathecode/events/migrations/0062_event_recording_url.py new file mode 100644 index 000000000..9c0dab18c --- /dev/null +++ b/breathecode/events/migrations/0062_event_recording_url.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.4 on 2025-01-28 12:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0061_event_is_public"), + ] + + operations = [ + migrations.AddField( + model_name="event", + name="recording_url", + field=models.URLField( + blank=True, + default=None, + help_text="This will be the URL of the workshop's recording, added once it's finished", + max_length=255, + null=True, + ), + ), + ] diff --git a/breathecode/events/models.py b/breathecode/events/models.py index 7e3da8716..39128ec09 100644 --- a/breathecode/events/models.py +++ b/breathecode/events/models.py @@ -219,6 +219,14 @@ def __init__(self, *args, **kwargs): help_text="This URL should have the URL of the meeting if it is an online event, if it's not online it should be empty.", ) + recording_url = models.URLField( + max_length=255, + null=True, + blank=True, + default=None, + help_text="This will be the URL of the workshop's recording, added once it's finished", + ) + starting_at = models.DateTimeField(blank=False) ending_at = models.DateTimeField( blank=False, help_text="This field contains the value of when the event is supposed to be finished." diff --git a/breathecode/events/serializers.py b/breathecode/events/serializers.py index 1133bbd37..6194fc582 100644 --- a/breathecode/events/serializers.py +++ b/breathecode/events/serializers.py @@ -191,6 +191,7 @@ class EventSmallSerializer(EventTinySerializer): author = UserSerializer(required=False) asset = serpy.MethodField() is_public = serpy.Field() + recording_url = serpy.Field() def get_asset(self, obj): if obj.asset_slug is not None: @@ -247,6 +248,7 @@ class EventSmallSerializerNoAcademy(serpy.Serializer): eventbrite_sync_description = serpy.Field() tags = serpy.Field() is_public = serpy.Field() + recording_url = serpy.Field() class EventPublicBigSerializer(EventSmallSerializer): @@ -299,6 +301,7 @@ class AcademyEventSmallSerializer(serpy.Serializer): free_for_all = serpy.Field() asset = serpy.MethodField() is_public = serpy.Field() + recording_url = serpy.Field() def get_asset(self, obj): if obj.asset_slug is not None: @@ -363,6 +366,17 @@ def validate(self, data: dict[str, Any]): academy = self.context.get("academy_id") + recording_url = data.get("recording_url") + if recording_url and not recording_url.startswith(("http://", "https://")): + raise ValidationException( + translation( + self.context.get("lang", "en"), + en="The recording URL must be a valid URL starting with http:// or https://", + es="La URL de la grabación debe ser una URL válida que comience con http:// o https://", + slug="invalid-recording-url", + ) + ) + if ("tags" not in data and self.instance.tags == "") or ("tags" in data and data["tags"] == ""): raise ValidationException( translation( @@ -457,6 +471,7 @@ class EventPUTSerializer(serializers.ModelSerializer): ending_at = serializers.DateTimeField(required=False) online_event = serializers.BooleanField(required=False) status = serializers.CharField(required=False) + recording_url = serializers.URLField(required=False) class Meta: model = Event diff --git a/breathecode/events/tests/actions/tests_update_or_create_event.py b/breathecode/events/tests/actions/tests_update_or_create_event.py index 4ec233191..9f5829c9d 100644 --- a/breathecode/events/tests/actions/tests_update_or_create_event.py +++ b/breathecode/events/tests/actions/tests_update_or_create_event.py @@ -201,6 +201,7 @@ def test_update_or_create_event__with_academy(self): "free_for_all": False, "uuid": uuid, "is_public": True, + "recording_url": None, } self.assertEqual(self.bc.database.list_of("events.Event"), [kwargs]) @@ -294,6 +295,7 @@ def test_update_or_create_event__with_event(self): "free_for_all": False, "uuid": uuid, "is_public": True, + "recording_url": None, } self.assertEqual(self.bc.database.list_of("events.Event"), [kwargs]) diff --git a/breathecode/events/tests/mixins/new_events_tests_case.py b/breathecode/events/tests/mixins/new_events_tests_case.py index 50d047987..cdc3d8ea7 100644 --- a/breathecode/events/tests/mixins/new_events_tests_case.py +++ b/breathecode/events/tests/mixins/new_events_tests_case.py @@ -81,6 +81,7 @@ def check_all_academy_events(self, models=None): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } for model in models ] diff --git a/breathecode/events/tests/urls/tests_academy_event.py b/breathecode/events/tests/urls/tests_academy_event.py index 47763d1e5..faa26efe1 100644 --- a/breathecode/events/tests/urls/tests_academy_event.py +++ b/breathecode/events/tests/urls/tests_academy_event.py @@ -54,6 +54,7 @@ def post_serializer(data={}): "live_stream_url": None, "host_user": None, "is_public": True, + "recording_url": None, **data, } @@ -97,6 +98,7 @@ def event_table(data={}): "sync_with_eventbrite": False, "currency": "", "is_public": True, + "recording_url": None, **data, } @@ -195,6 +197,7 @@ def test_all_academy_events_correct_city(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] @@ -269,6 +272,7 @@ def test_all_academy_events_correct_country(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] @@ -343,6 +347,7 @@ def test_all_academy_events_correct_zip_code(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] @@ -398,6 +403,7 @@ def test_all_academy_events_upcoming(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] @@ -687,6 +693,7 @@ def test_all_academy_events__post_with_event_is_public_true(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] self.assertEqual(json, expected) @@ -741,6 +748,7 @@ def test_all_academy_events__post_with_event_is_public_false(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] self.assertEqual(json, expected) @@ -794,6 +802,7 @@ def test_all_academy_events__post_with_event_is_public_empty(self): "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, "is_public": model["event"].is_public, + "recording_url": model["event"].recording_url, } ] diff --git a/breathecode/events/tests/urls/tests_academy_event_id.py b/breathecode/events/tests/urls/tests_academy_event_id.py index a73072318..03a8a54b0 100644 --- a/breathecode/events/tests/urls/tests_academy_event_id.py +++ b/breathecode/events/tests/urls/tests_academy_event_id.py @@ -71,6 +71,7 @@ def get_serializer(event, academy, asset=None, data={}): "eventbrite_sync_description": event.eventbrite_sync_description, "asset": asset_serializer(asset) if asset else None, "is_public": event.is_public, + "recording_url": event.recording_url, **data, } @@ -218,6 +219,7 @@ def test_academy_event_id__put__is_public_true(self): "free_for_all": False, "uuid": str(uuid), "is_public": True, + "recording_url": None, **data, } @@ -316,6 +318,7 @@ def test_academy_event_id__put__is_public_false(self): "free_for_all": False, "uuid": str(uuid), "is_public": False, + "recording_url": None, **data, } @@ -666,6 +669,7 @@ def test_academy_cohort_id__put(self): "free_for_all": False, "uuid": str(uuid), "is_public": True, + "recording_url": None, **data, } @@ -900,6 +904,7 @@ def test_academy_cohort_id__put__with_tags(self): "free_for_all": False, "uuid": str(uuid), "is_public": True, + "recording_url": None, **data, } @@ -1001,6 +1006,7 @@ def test_academy_cohort_id__put__with_duplicate_tags(self): "free_for_all": False, "uuid": str(uuid), "is_public": True, + "recording_url": None, **data, } diff --git a/breathecode/events/tests/urls/tests_all.py b/breathecode/events/tests/urls/tests_all.py index be40d126a..b7754f6fd 100644 --- a/breathecode/events/tests/urls/tests_all.py +++ b/breathecode/events/tests/urls/tests_all.py @@ -35,6 +35,7 @@ def serialize_event(event): "ended_at": (event.ended_at.strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" if event.ended_at else None), "online_event": event.online_event, "is_public": event.is_public, + "recording_url": event.recording_url, "venue": ( None if not event.venue diff --git a/breathecode/events/tests/urls/tests_me.py b/breathecode/events/tests/urls/tests_me.py index 712132be9..2fa37a5c6 100644 --- a/breathecode/events/tests/urls/tests_me.py +++ b/breathecode/events/tests/urls/tests_me.py @@ -144,6 +144,7 @@ def get_serializer( "url": event.url, "venue": event.venue, "is_public": event.is_public, + "recording_url": event.recording_url, **data, } diff --git a/breathecode/events/tests/urls/tests_me_event_id.py b/breathecode/events/tests/urls/tests_me_event_id.py index 82af318a7..2bc05c5ce 100644 --- a/breathecode/events/tests/urls/tests_me_event_id.py +++ b/breathecode/events/tests/urls/tests_me_event_id.py @@ -146,6 +146,7 @@ def get_serializer( "url": event.url, "venue": event.venue, "is_public": event.is_public, + "recording_url": event.recording_url, **data, } diff --git a/breathecode/events/views.py b/breathecode/events/views.py index 7375d2024..f3c0c431e 100644 --- a/breathecode/events/views.py +++ b/breathecode/events/views.py @@ -137,6 +137,12 @@ def get_events(request): elif is_public == "false": lookup["is_public"] = False + recording_url = request.GET.get("recording_url", None) + if recording_url == "true": + lookup["recording_url"] = True + elif recording_url == "false": + lookup["recording_url"] = False + if "technologies" in request.GET: values = request.GET.get("technologies").split(",") tech_query = Q() diff --git a/breathecode/services/eventbrite/actions/event_created.py b/breathecode/services/eventbrite/actions/event_created.py index 3fd82ffb8..9c0ba528f 100644 --- a/breathecode/services/eventbrite/actions/event_created.py +++ b/breathecode/services/eventbrite/actions/event_created.py @@ -6,8 +6,8 @@ def event_created(self, webhook, payload: dict): # lazyload to fix circular import - from breathecode.events.models import Organization from breathecode.events.actions import update_or_create_event + from breathecode.events.models import Organization org = Organization.objects.filter(id=webhook.organization_id).first()