diff --git a/breathecode/events/migrations/0060_eventtype_technologies.py b/breathecode/events/migrations/0060_eventtype_technologies.py new file mode 100644 index 000000000..e3ecbfba6 --- /dev/null +++ b/breathecode/events/migrations/0060_eventtype_technologies.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.2 on 2024-11-18 15:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0059_event_asset_slug"), + ] + + operations = [ + migrations.AddField( + model_name="eventtype", + name="technologies", + field=models.CharField( + blank=True, + default=None, + help_text="Add comma-separated list of technologies", + max_length=200, + null=True, + ), + ), + ] diff --git a/breathecode/events/migrations/0061_event_is_public.py b/breathecode/events/migrations/0061_event_is_public.py new file mode 100644 index 000000000..2efd73db2 --- /dev/null +++ b/breathecode/events/migrations/0061_event_is_public.py @@ -0,0 +1,21 @@ +# Generated by Django 5.1.2 on 2024-11-25 17:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("events", "0060_eventtype_technologies"), + ] + + operations = [ + migrations.AddField( + model_name="event", + name="is_public", + field=models.BooleanField( + default=True, + help_text="If true, then it will be shown in cards and the workshop's landing page. Otherwise it will be hidden.", + ), + ), + ] diff --git a/breathecode/events/models.py b/breathecode/events/models.py index 466dbe5f5..7e3da8716 100644 --- a/breathecode/events/models.py +++ b/breathecode/events/models.py @@ -134,6 +134,10 @@ class EventType(models.Model): default=True, help_text="Other academies are allowed to create events of this type" ) + technologies = models.CharField( + max_length=200, null=True, default=None, blank=True, help_text="Add comma-separated list of technologies" + ) + created_at = models.DateTimeField(auto_now_add=True, editable=False) updated_at = models.DateTimeField(auto_now=True, editable=False) @@ -272,6 +276,13 @@ def __init__(self, *args, **kwargs): created_at = models.DateTimeField(auto_now_add=True, editable=False) updated_at = models.DateTimeField(auto_now=True, editable=False) + is_public = models.BooleanField( + default=True, + blank=False, + null=False, + help_text="If true, then it will be shown in cards and the workshop's landing page. Otherwise it will be hidden.", + ) + def __str__(self): return self.title or "No title" diff --git a/breathecode/events/serializers.py b/breathecode/events/serializers.py index d3d86812e..1133bbd37 100644 --- a/breathecode/events/serializers.py +++ b/breathecode/events/serializers.py @@ -80,6 +80,7 @@ class EventTypeSmallSerializer(serpy.Serializer): id = serpy.Field() slug = serpy.Field() name = serpy.Field() + technologies = serpy.Field() class EventTypeSerializer(EventTypeSmallSerializer): @@ -189,6 +190,7 @@ class EventSmallSerializer(EventTinySerializer): host_user = UserBigSerializer(required=False) author = UserSerializer(required=False) asset = serpy.MethodField() + is_public = serpy.Field() def get_asset(self, obj): if obj.asset_slug is not None: @@ -244,6 +246,7 @@ class EventSmallSerializerNoAcademy(serpy.Serializer): eventbrite_sync_status = serpy.Field() eventbrite_sync_description = serpy.Field() tags = serpy.Field() + is_public = serpy.Field() class EventPublicBigSerializer(EventSmallSerializer): @@ -295,6 +298,7 @@ class AcademyEventSmallSerializer(serpy.Serializer): author = UserSerializer(required=False) free_for_all = serpy.Field() asset = serpy.MethodField() + is_public = serpy.Field() def get_asset(self, obj): if obj.asset_slug is not None: diff --git a/breathecode/events/tests/mixins/new_events_tests_case.py b/breathecode/events/tests/mixins/new_events_tests_case.py index cb4055556..50d047987 100644 --- a/breathecode/events/tests/mixins/new_events_tests_case.py +++ b/breathecode/events/tests/mixins/new_events_tests_case.py @@ -3,16 +3,18 @@ """ import os + from django.urls import reverse_lazy from rest_framework.test import APITestCase + from breathecode.tests.mixins import ( - GenerateModelsMixin, + BreathecodeMixin, CacheMixin, + DatetimeMixin, + GenerateModelsMixin, GenerateQueriesMixin, HeadersMixin, - DatetimeMixin, ICallMixin, - BreathecodeMixin, ) @@ -78,6 +80,7 @@ def check_all_academy_events(self, models=None): "sync_with_eventbrite": model["event"].sync_with_eventbrite, "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, } 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 943f0a1dc..47763d1e5 100644 --- a/breathecode/events/tests/urls/tests_academy_event.py +++ b/breathecode/events/tests/urls/tests_academy_event.py @@ -53,6 +53,7 @@ def post_serializer(data={}): "currency": "USD", "live_stream_url": None, "host_user": None, + "is_public": True, **data, } @@ -95,6 +96,7 @@ def event_table(data={}): "live_stream_url": None, "sync_with_eventbrite": False, "currency": "", + "is_public": True, **data, } @@ -192,6 +194,7 @@ def test_all_academy_events_correct_city(self): "sync_with_eventbrite": model["event"].sync_with_eventbrite, "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, } ] @@ -265,6 +268,7 @@ def test_all_academy_events_correct_country(self): "sync_with_eventbrite": model["event"].sync_with_eventbrite, "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, } ] @@ -338,6 +342,7 @@ def test_all_academy_events_correct_zip_code(self): "sync_with_eventbrite": model["event"].sync_with_eventbrite, "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, } ] @@ -392,6 +397,7 @@ def test_all_academy_events_upcoming(self): "sync_with_eventbrite": model["event"].sync_with_eventbrite, "eventbrite_sync_description": model["event"].eventbrite_sync_description, "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, } ] @@ -628,6 +634,172 @@ def test_all_academy_events__post__bad_tags__one_of_two_tags_not_exists(self): self.assertEqual(response.status_code, 400) self.assertEqual(self.bc.database.list_of("events.Event"), []) + """ + 🔽🔽🔽 Post is_public + """ + + def test_all_academy_events__post_with_event_is_public_true(self): + self.headers(academy=1) + model = self.generate_models( + authenticate=True, + profile_academy=True, + capability="read_event", + role="potato", + syllabus=True, + venue=True, + event=True, + is_public=True, + ) + url = reverse_lazy("events:academy_event") + + response = self.client.get(url) + json = response.json() + + expected = [ + { + "banner": model["event"].banner, + "ending_at": datetime_to_iso_format(model["event"].ending_at), + "event_type": model["event"].event_type, + "excerpt": model["event"].excerpt, + "tags": model["event"].tags, + "slug": model["event"].slug, + "id": model["event"].id, + "lang": model["event"].lang, + "online_event": model["event"].online_event, + "starting_at": datetime_to_iso_format(model["event"].starting_at), + "ended_at": model["event"].ended_at, + "status": model["event"].status, + "title": model["event"].title, + "url": model["event"].url, + "host": model["event"].host, + "asset_slug": model["event"].asset_slug, + "capacity": model["event"].capacity, + "venue": { + "city": model["event"].venue.city, + "id": model["event"].id, + "state": model["event"].venue.state, + "street_address": model["event"].venue.street_address, + "title": model["event"].venue.title, + "zip_code": model["event"].venue.zip_code, + "updated_at": self.bc.datetime.to_iso_string(model.venue.updated_at), + }, + "sync_with_eventbrite": model["event"].sync_with_eventbrite, + "eventbrite_sync_description": model["event"].eventbrite_sync_description, + "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, + } + ] + self.assertEqual(json, expected) + self.assertEqual(response.status_code, 200) + + def test_all_academy_events__post_with_event_is_public_false(self): + self.headers(academy=1) + model = self.generate_models( + authenticate=True, + profile_academy=True, + capability="read_event", + role="potato", + syllabus=True, + venue=True, + event=True, + is_public=False, + ) + url = reverse_lazy("events:academy_event") + + response = self.client.get(url) + json = response.json() + + expected = [ + { + "banner": model["event"].banner, + "ending_at": datetime_to_iso_format(model["event"].ending_at), + "event_type": model["event"].event_type, + "excerpt": model["event"].excerpt, + "tags": model["event"].tags, + "slug": model["event"].slug, + "id": model["event"].id, + "lang": model["event"].lang, + "online_event": model["event"].online_event, + "starting_at": datetime_to_iso_format(model["event"].starting_at), + "ended_at": model["event"].ended_at, + "status": model["event"].status, + "title": model["event"].title, + "url": model["event"].url, + "host": model["event"].host, + "asset_slug": model["event"].asset_slug, + "capacity": model["event"].capacity, + "venue": { + "city": model["event"].venue.city, + "id": model["event"].id, + "state": model["event"].venue.state, + "street_address": model["event"].venue.street_address, + "title": model["event"].venue.title, + "zip_code": model["event"].venue.zip_code, + "updated_at": self.bc.datetime.to_iso_string(model.venue.updated_at), + }, + "sync_with_eventbrite": model["event"].sync_with_eventbrite, + "eventbrite_sync_description": model["event"].eventbrite_sync_description, + "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, + } + ] + self.assertEqual(json, expected) + self.assertEqual(response.status_code, 200) + + def test_all_academy_events__post_with_event_is_public_empty(self): + self.headers(academy=1) + model = self.generate_models( + authenticate=True, + profile_academy=True, + capability="read_event", + role="potato", + syllabus=True, + venue=True, + event=True, + ) + url = reverse_lazy("events:academy_event") + + response = self.client.get(url) + json = response.json() + + expected = [ + { + "banner": model["event"].banner, + "ending_at": datetime_to_iso_format(model["event"].ending_at), + "event_type": model["event"].event_type, + "excerpt": model["event"].excerpt, + "tags": model["event"].tags, + "slug": model["event"].slug, + "id": model["event"].id, + "lang": model["event"].lang, + "online_event": model["event"].online_event, + "starting_at": datetime_to_iso_format(model["event"].starting_at), + "ended_at": model["event"].ended_at, + "status": model["event"].status, + "title": model["event"].title, + "url": model["event"].url, + "host": model["event"].host, + "asset_slug": model["event"].asset_slug, + "capacity": model["event"].capacity, + "venue": { + "city": model["event"].venue.city, + "id": model["event"].id, + "state": model["event"].venue.state, + "street_address": model["event"].venue.street_address, + "title": model["event"].venue.title, + "zip_code": model["event"].venue.zip_code, + "updated_at": self.bc.datetime.to_iso_string(model.venue.updated_at), + }, + "sync_with_eventbrite": model["event"].sync_with_eventbrite, + "eventbrite_sync_description": model["event"].eventbrite_sync_description, + "eventbrite_sync_status": model["event"].eventbrite_sync_status, + "is_public": model["event"].is_public, + } + ] + + self.assertEqual(json, expected) + self.assertEqual(response.status_code, 200) + """ 🔽🔽🔽 Post bad slug """ diff --git a/breathecode/events/tests/urls/tests_academy_event_id.py b/breathecode/events/tests/urls/tests_academy_event_id.py index 2186d0d6e..a73072318 100644 --- a/breathecode/events/tests/urls/tests_academy_event_id.py +++ b/breathecode/events/tests/urls/tests_academy_event_id.py @@ -70,6 +70,7 @@ def get_serializer(event, academy, asset=None, data={}): "eventbrite_sync_status": event.eventbrite_sync_status, "eventbrite_sync_description": event.eventbrite_sync_description, "asset": asset_serializer(asset) if asset else None, + "is_public": event.is_public, **data, } @@ -140,6 +141,203 @@ def test_academy_event_id_valid_id(self): self.assertEqual(json, expected) self.assertEqual(response.status_code, 200) + """ + 🔽🔽🔽 Put, is_public true + """ + + @patch("breathecode.marketing.signals.downloadable_saved.send_robust", MagicMock()) + @patch("uuid.uuid4", PropertyMock(MagicMock=uuid)) + @patch("os.urandom", MagicMock(return_value=seed)) + def test_academy_event_id__put__is_public_true(self): + """Test /cohort without auth""" + self.headers(academy=1) + event_kwargs = {"is_public": True} + model = self.generate_models( + authenticate=True, + organization=True, + profile_academy=True, + academy=True, + active_campaign_academy=True, + tag=(2, {"tag_type": "DISCOVERY"}), + capability="crud_event", + role="potato2", + event_type=1, + event=True, + event_kwargs=event_kwargs, + ) + + url = reverse_lazy("events:academy_event_id", kwargs={"event_id": 1}) + current_date = self.datetime_now() + data = { + "id": 1, + "url": "https://www.google.com/", + "banner": "https://www.google.com/banner", + "tags": ",".join([x.slug for x in model.tag]), + "capacity": 11, + "starting_at": self.datetime_to_iso(current_date), + "ending_at": self.datetime_to_iso(current_date), + } + + response = self.client.put(url, data, format="json") + json = response.json() + + del json["updated_at"] + del json["created_at"] + + expected = { + "academy": 1, + "author": 1, + "description": None, + "event_type": 1, + "eventbrite_id": None, + "eventbrite_organizer_id": None, + "eventbrite_status": None, + "eventbrite_url": None, + "excerpt": None, + "host": model["event"].host, + "id": 2, + "lang": "en", + "slug": None, + "online_event": False, + "free_for_all": False, + "organization": 1, + "published_at": None, + "asset_slug": None, + "status": "DRAFT", + "eventbrite_sync_description": None, + "eventbrite_sync_status": "PENDING", + "title": None, + "venue": None, + "sync_with_eventbrite": False, + "ended_at": None, + "eventbrite_sync_status": "PENDING", + "currency": "USD", + "live_stream_url": None, + "host_user": 1, + "free_for_bootcamps": True, + "free_for_all": False, + "uuid": str(uuid), + "is_public": True, + **data, + } + + self.assertEqual(json, expected) + self.assertEqual(response.status_code, 200) + self.assertEqual( + self.bc.database.list_of("events.Event"), + [ + { + **self.model_to_dict(model, "event"), + **data, + "organization_id": 1, + "starting_at": current_date, + "ending_at": current_date, + "free_for_bootcamps": True, + "event_type_id": 1, + "lang": "en", + } + ], + ) + + """ + 🔽🔽🔽 Put, is_public false + """ + + @patch("breathecode.marketing.signals.downloadable_saved.send_robust", MagicMock()) + @patch("uuid.uuid4", PropertyMock(MagicMock=uuid)) + @patch("os.urandom", MagicMock(return_value=seed)) + def test_academy_event_id__put__is_public_false(self): + """Test /cohort without auth""" + self.headers(academy=1) + event_kwargs = {"is_public": False} + model = self.generate_models( + authenticate=True, + organization=True, + profile_academy=True, + academy=True, + active_campaign_academy=True, + tag=(2, {"tag_type": "DISCOVERY"}), + capability="crud_event", + role="potato2", + event_type=1, + event=True, + event_kwargs=event_kwargs, + ) + + url = reverse_lazy("events:academy_event_id", kwargs={"event_id": 1}) + current_date = self.datetime_now() + data = { + "id": 1, + "url": "https://www.google.com/", + "banner": "https://www.google.com/banner", + "tags": ",".join([x.slug for x in model.tag]), + "capacity": 11, + "starting_at": self.datetime_to_iso(current_date), + "ending_at": self.datetime_to_iso(current_date), + } + + response = self.client.put(url, data, format="json") + json = response.json() + + del json["updated_at"] + del json["created_at"] + + expected = { + "academy": 1, + "author": 1, + "description": None, + "event_type": 1, + "eventbrite_id": None, + "eventbrite_organizer_id": None, + "eventbrite_status": None, + "eventbrite_url": None, + "excerpt": None, + "host": model["event"].host, + "id": 2, + "lang": "en", + "slug": None, + "online_event": False, + "free_for_all": False, + "organization": 1, + "published_at": None, + "asset_slug": None, + "status": "DRAFT", + "eventbrite_sync_description": None, + "eventbrite_sync_status": "PENDING", + "title": None, + "venue": None, + "sync_with_eventbrite": False, + "ended_at": None, + "eventbrite_sync_status": "PENDING", + "currency": "USD", + "live_stream_url": None, + "host_user": 1, + "free_for_bootcamps": True, + "free_for_all": False, + "uuid": str(uuid), + "is_public": False, + **data, + } + + self.assertEqual(json, expected) + self.assertEqual(response.status_code, 200) # Verificamos que la respuesta sea correcta + self.assertEqual( + self.bc.database.list_of("events.Event"), + [ + { + **self.model_to_dict(model, "event"), + **data, + "organization_id": 1, + "starting_at": current_date, + "ending_at": current_date, + "free_for_bootcamps": True, + "event_type_id": 1, + "lang": "en", + "is_public": False, + } + ], + ) + """ 🔽🔽🔽 Put - bad tags """ @@ -467,6 +665,7 @@ def test_academy_cohort_id__put(self): "free_for_bootcamps": True, "free_for_all": False, "uuid": str(uuid), + "is_public": True, **data, } @@ -700,6 +899,7 @@ def test_academy_cohort_id__put__with_tags(self): "free_for_bootcamps": True, "free_for_all": False, "uuid": str(uuid), + "is_public": True, **data, } @@ -800,6 +1000,7 @@ def test_academy_cohort_id__put__with_duplicate_tags(self): "free_for_bootcamps": True, "free_for_all": False, "uuid": str(uuid), + "is_public": True, **data, } diff --git a/breathecode/events/tests/urls/tests_academy_eventype.py b/breathecode/events/tests/urls/tests_academy_eventype.py index eb9936c3f..e123d4f2e 100644 --- a/breathecode/events/tests/urls/tests_academy_eventype.py +++ b/breathecode/events/tests/urls/tests_academy_eventype.py @@ -34,6 +34,7 @@ def get_serializer(event_type, academy=None, city=None, data={}): "slug": event_type.slug, "lang": event_type.lang, "description": event_type.description, + "technologies": event_type.technologies, **data, } @@ -288,6 +289,7 @@ def test_post_event_type(self): "description": "Potato", "icon_url": "https://www.google.com", "lang": "en", + "technologies": "", } url = reverse_lazy("events:academy_eventype") diff --git a/breathecode/events/tests/urls/tests_academy_eventype_slug.py b/breathecode/events/tests/urls/tests_academy_eventype_slug.py index 916d6fd73..f0fe91b41 100644 --- a/breathecode/events/tests/urls/tests_academy_eventype_slug.py +++ b/breathecode/events/tests/urls/tests_academy_eventype_slug.py @@ -1,11 +1,13 @@ from unittest.mock import MagicMock, call, patch -from breathecode.events.caches import EventCache + from django.urls.base import reverse_lazy +from django.utils import timezone +from breathecode.events.caches import EventCache +from breathecode.services import datetime_to_iso_format from breathecode.utils.api_view_extensions.api_view_extension_handlers import APIViewExtensionHandlers + from ..mixins.new_events_tests_case import EventTestCase -from breathecode.services import datetime_to_iso_format -from django.utils import timezone def get_serializer(event_type, academy=None, city=None, data={}): @@ -35,6 +37,7 @@ def get_serializer(event_type, academy=None, city=None, data={}): "allow_shared_creation": event_type.allow_shared_creation, "description": event_type.description, "visibility_settings": event_type.visibility_settings, + "technologies": event_type.technologies, **data, } @@ -51,6 +54,7 @@ def put_serializer(event_type, data={}): "allow_shared_creation": event_type.allow_shared_creation, "free_for_bootcamps": event_type.free_for_bootcamps, "description": event_type.description, + "technologies": event_type.technologies, **data, } diff --git a/breathecode/events/tests/urls/tests_all.py b/breathecode/events/tests/urls/tests_all.py new file mode 100644 index 000000000..be805e75e --- /dev/null +++ b/breathecode/events/tests/urls/tests_all.py @@ -0,0 +1,419 @@ +from datetime import datetime, timedelta, timezone + +import capyc.pytest as capy +from django.urls.base import reverse_lazy + + +def serialize_event(event): + return { + "id": event.id, + "title": event.title, + "starting_at": ( + event.starting_at.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" + if isinstance(event.starting_at, datetime) + else None + ), + "ending_at": ( + event.ending_at.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z" + if isinstance(event.ending_at, datetime) + else None + ), + "event_type": { + "id": event.event_type.id, + "slug": event.event_type.slug, + "name": event.event_type.name, + "technologies": event.event_type.technologies, + }, + "slug": event.slug, + "excerpt": event.excerpt, + "lang": event.lang, + "url": event.url, + "banner": event.banner, + "description": event.description, + "capacity": event.capacity, + "status": event.status, + "host": event.host, + "ended_at": ( + event.ended_at.astimezone(timezone.utc).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, + "venue": ( + None + if not event.venue + else { + "id": event.venue.id, + "title": event.venue.title, + "street_address": event.venue.street_address, + "city": event.venue.city.name, + "zip_code": event.venue.zip_code, + "state": event.venue.state, + "updated_at": event.venue.updated_at.isoformat(), + } + ), + "academy": ( + None + if not event.academy + else { + "id": event.academy.id, + "slug": event.academy.slug, + "name": event.academy.name, + "city": {"name": event.academy.city.name} if event.academy.city else None, + } + ), + "sync_with_eventbrite": event.sync_with_eventbrite, + "eventbrite_sync_status": event.eventbrite_sync_status, + "eventbrite_sync_description": event.eventbrite_sync_description, + "tags": event.tags, + "asset_slug": event.asset_slug, + "host_user": ( + None + if not event.host_user + else { + "id": event.host_user.id, + "first_name": event.host_user.first_name, + "last_name": event.host_user.last_name, + } + ), + "author": ( + None + if not event.author + else { + "id": event.author.id, + "first_name": event.author.first_name, + "last_name": event.author.last_name, + } + ), + "asset": None, + } + + +def test_filter_by_technologies(client: capy.Client, database: capy.Database, fake: capy.Fake): + url = reverse_lazy("events:all") + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=[ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "flask, pandas", + }, + ], + event=[ + { + "title": "My First Event", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + } + for n in range(0, 2) + ], + ) + + response = client.get(f"{url}?technologies=python") + json = response.json() + + expected = [serialize_event(event) for event in model.event if "python" in event.event_type.technologies] + + assert response.status_code == 200 + assert len(json) == 1 + assert expected == json + + +def test_filter_by_technologies_obtain_two(client: capy.Client, database: capy.Database, fake: capy.Fake): + url = reverse_lazy("events:all") + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=[ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "flask, pandas", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description3", + "technologies": "javascript, java", + }, + ], + event=[ + { + "title": f"My Event {n + 1}", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + } + for n in range(3) + ], + ) + + response = client.get(f"{url}?technologies=python,java") + json = response.json() + + technologies_to_filter = {"python", "java"} + expected = [ + serialize_event(event) + for event in model.event + if any(tech in event.event_type.technologies.split(", ") for tech in technologies_to_filter) + ] + + assert response.status_code == 200 + assert len(json) == 2 + assert expected == json + + +def test_all_academy_events_get_with_event_is_public_true_in_filter_is_public_true( + client: capy.Client, database: capy.Database, fake: capy.Fake +): + url = reverse_lazy("events:all") + + event_types = [ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "javascript, react", + }, + ] + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=event_types, + event=[ + { + "title": f"My Event {n + 1}", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + "is_public": True, + } + for n in range(len(event_types)) + ], + ) + + response = client.get(f"{url}?is_public=true") + json = response.json() + + expected = [serialize_event(event) for event in model.event if event.is_public] + + assert response.status_code == 200 + assert len(json) == len(expected) + assert expected == json + + +def test_all_academy_events_get_with_event_is_public_false_in_filter_is_public_true( + client: capy.Client, database: capy.Database, fake: capy.Fake +): + url = reverse_lazy("events:all") + + event_types = [ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "javascript, react", + }, + ] + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=event_types, + event=[ + { + "title": f"My Event {n + 1}", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + "is_public": False, + } + for n in range(len(event_types)) + ], + ) + + response = client.get(f"{url}?is_public=true") + json = response.json() + + expected = [serialize_event(event) for event in model.event if event.is_public] + + assert response.status_code == 200 + assert len(json) == len(expected) + assert expected == json + + +def test_all_academy_events_get_with_event_is_public_false_in_filter_is_public_false( + client: capy.Client, database: capy.Database, fake: capy.Fake +): + url = reverse_lazy("events:all") + + event_types = [ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "javascript, react", + }, + ] + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=event_types, + event=[ + { + "title": f"My Event {n + 1}", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + "is_public": False, + } + for n in range(len(event_types)) + ], + ) + + response = client.get(f"{url}?is_public=false") + json = response.json() + + expected = [serialize_event(event) for event in model.event if event.is_public == False] + + assert response.status_code == 200 + assert len(json) == len(expected) + assert expected == json + + +def test_all_academy_events_get_with_event_is_public_true_in_filter_is_public_false( + client: capy.Client, database: capy.Database, fake: capy.Fake +): + url = reverse_lazy("events:all") + + event_types = [ + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description1", + "technologies": "python, flask", + }, + { + "slug": fake.slug(), + "name": fake.name(), + "description": "description2", + "technologies": "javascript, react", + }, + ] + + model = database.create( + city=1, + country=1, + academy={ + "slug": fake.slug(), + "name": fake.name(), + "logo_url": "https://example.com/logo.jpg", + "street_address": "Address", + }, + event_type=event_types, + event=[ + { + "title": f"My Event {n + 1}", + "capacity": 100, + "banner": "https://example.com/banner.jpg", + "starting_at": datetime.now(), + "ending_at": datetime.now() + timedelta(hours=2), + "status": "ACTIVE", + "event_type_id": n + 1, + "is_public": True, + } + for n in range(len(event_types)) + ], + ) + + response = client.get(f"{url}?is_public=false") + json = response.json() + + expected = [serialize_event(event) for event in model.event if event.is_public == False] + + assert response.status_code == 200 + assert len(json) == len(expected) + assert expected == json diff --git a/breathecode/events/tests/urls/tests_eventype.py b/breathecode/events/tests/urls/tests_eventype.py index 9288d87f3..b31ccef5b 100644 --- a/breathecode/events/tests/urls/tests_eventype.py +++ b/breathecode/events/tests/urls/tests_eventype.py @@ -1,11 +1,13 @@ from unittest.mock import MagicMock, call, patch -from breathecode.events.caches import EventCache + from django.urls.base import reverse_lazy +from django.utils import timezone +from breathecode.events.caches import EventCache +from breathecode.services import datetime_to_iso_format from breathecode.utils.api_view_extensions.api_view_extension_handlers import APIViewExtensionHandlers + from ..mixins.new_events_tests_case import EventTestCase -from breathecode.services import datetime_to_iso_format -from django.utils import timezone def get_serializer(event_type, academy=None, city=None, data={}): @@ -32,6 +34,7 @@ def get_serializer(event_type, academy=None, city=None, data={}): "slug": event_type.slug, "lang": event_type.lang, "description": event_type.description, + "technologies": event_type.technologies, **data, } @@ -73,6 +76,7 @@ def test_academy_event_type_with_results(self): "created_at": timezone.now(), "updated_at": timezone.now(), "icon_url": "https://www.google.com", + "technologies": None, } model = self.generate_models( authenticate=True, event=True, event_type=True, event_type_kwargs=event_type_kwargs diff --git a/breathecode/events/tests/urls/tests_me.py b/breathecode/events/tests/urls/tests_me.py index fb8866420..712132be9 100644 --- a/breathecode/events/tests/urls/tests_me.py +++ b/breathecode/events/tests/urls/tests_me.py @@ -114,6 +114,7 @@ def get_serializer( "allow_shared_creation": event_type.allow_shared_creation, "description": event_type.description, "visibility_settings": visibility_settings_serializer(event_type.visibility_settings), + "technologies": event_type.technologies, }, "eventbrite_id": event.eventbrite_id, "eventbrite_organizer_id": event.eventbrite_organizer_id, @@ -142,6 +143,7 @@ def get_serializer( "updated_at": self.bc.datetime.to_iso_string(event.updated_at), "url": event.url, "venue": event.venue, + "is_public": event.is_public, **data, } diff --git a/breathecode/events/tests/urls/tests_me_event_id.py b/breathecode/events/tests/urls/tests_me_event_id.py index d1ed7837a..82af318a7 100644 --- a/breathecode/events/tests/urls/tests_me_event_id.py +++ b/breathecode/events/tests/urls/tests_me_event_id.py @@ -121,6 +121,7 @@ def get_serializer( "allow_shared_creation": event_type.allow_shared_creation, "description": event_type.description, "visibility_settings": visibility_settings_serializer(event_type.visibility_settings), + "technologies": event_type.technologies, }, "eventbrite_id": event.eventbrite_id, "eventbrite_organizer_id": event.eventbrite_organizer_id, @@ -144,6 +145,7 @@ def get_serializer( "updated_at": self.bc.datetime.to_iso_string(event.updated_at), "url": event.url, "venue": event.venue, + "is_public": event.is_public, **data, } diff --git a/breathecode/events/views.py b/breathecode/events/views.py index bf7625fcf..d3a70a2b3 100644 --- a/breathecode/events/views.py +++ b/breathecode/events/views.py @@ -131,6 +131,19 @@ def get_events(request): elif online_event == "false": lookup["online_event"] = False + is_public = request.GET.get("is_public", None) + if is_public == "true": + lookup["is_public"] = True + elif is_public == "false": + lookup["is_public"] = False + + if "technologies" in request.GET: + values = request.GET.get("technologies").split(",") + tech_query = Q() + for value in values: + tech_query |= Q(event_type__technologies__icontains=value.strip()) + items = items.filter(tech_query) + lookup["ending_at__gte"] = timezone.now() if "past" in request.GET: if request.GET.get("past") == "true": diff --git a/breathecode/registry/views.py b/breathecode/registry/views.py index 4a7911c28..bd7f63675 100644 --- a/breathecode/registry/views.py +++ b/breathecode/registry/views.py @@ -218,6 +218,10 @@ def get(self, request, tech_slug=None): if like and like not in ["undefined", ""]: items = items.filter(Q(slug__icontains=like) | Q(title__icontains=like)) + if "visibility" in request.GET: + visibility_param = request.GET.get("visibility") + items = items.filter(visibility__iexact=visibility_param) + items = handler.queryset(items) serializer = AssetTechnologySerializer(items, many=True) diff --git a/breathecode/services/__init__.py b/breathecode/services/__init__.py index 86f51a68e..acb1c6e75 100644 --- a/breathecode/services/__init__.py +++ b/breathecode/services/__init__.py @@ -1,6 +1,7 @@ from .datetime_to_iso_format import datetime_to_iso_format # noqa: F401 from .eventbrite import CAMPAIGN, SOURCE, Eventbrite # noqa: F401 +from .google_apps import * # noqa: F401 from .google_cloud import Datastore, Function, Storage # noqa: F401 from .google_meet import * # noqa: F401 -from .google_apps import * # noqa: F401 -from .launch_darkly import * # noqa: F401 + +# from .launch_darkly import * # noqa: F401 diff --git a/breathecode/services/launch_darkly/__init__.py b/breathecode/services/launch_darkly/__init__.py index a5658ab9b..5971ed15c 100644 --- a/breathecode/services/launch_darkly/__init__.py +++ b/breathecode/services/launch_darkly/__init__.py @@ -1 +1 @@ -from .client import * # noqa: F401 +# from .client import * # noqa: F401 diff --git a/breathecode/services/launch_darkly/client.py b/breathecode/services/launch_darkly/client.py index 5ff9bc0a6..5ee090542 100644 --- a/breathecode/services/launch_darkly/client.py +++ b/breathecode/services/launch_darkly/client.py @@ -1,53 +1,53 @@ -import logging, os -import re -from typing import Any -import ldclient -from ldclient.config import Config -from ldclient import Context, LDClient +# import logging, os +# import re +# from typing import Any +# import ldclient +# from ldclient.config import Config +# from ldclient import Context, LDClient -logger = logging.getLogger(__name__) +# logger = logging.getLogger(__name__) -__all__ = ["LaunchDarkly"] +# __all__ = ["LaunchDarkly"] -clients: dict[str, LDClient] = {} +# clients: dict[str, LDClient] = {} -# docs https://docs.launchdarkly.com/sdk/server-side/python/migration-7-to-8 -class LaunchDarkly: - client: LDClient +# # docs https://docs.launchdarkly.com/sdk/server-side/python/migration-7-to-8 +# class LaunchDarkly: +# client: LDClient - def __init__(self, api_key=None): - api_key = api_key or os.getenv("LAUNCH_DARKLY_API_KEY") +# def __init__(self, api_key=None): +# api_key = api_key or os.getenv("LAUNCH_DARKLY_API_KEY") - if api_key not in clients: - config = Config(api_key) - ldclient.set_config(config) - clients[api_key] = ldclient.get() +# if api_key not in clients: +# config = Config(api_key) +# ldclient.set_config(config) +# clients[api_key] = ldclient.get() - self.client = clients[api_key] +# self.client = clients[api_key] - def get(self, key, context, default=None) -> Any: - return self.client.variation(key, context, default) +# def get(self, key, context, default=None) -> Any: +# return self.client.variation(key, context, default) - def get_evaluation_reason(self, key, context, default=None) -> Any: - return self.client.variation_detail(key, context, default) +# def get_evaluation_reason(self, key, context, default=None) -> Any: +# return self.client.variation_detail(key, context, default) - def _validate_key(self, key): - if not re.findall(r"^[a-zA-Z0-9_\-\.]+$", key): - raise ValueError( - "The chosen key is invalid, it just must incluse letters, numbers, " "underscore, dash and dot" - ) +# def _validate_key(self, key): +# if not re.findall(r"^[a-zA-Z0-9_\-\.]+$", key): +# raise ValueError( +# "The chosen key is invalid, it just must incluse letters, numbers, " "underscore, dash and dot" +# ) - def context(self, key: str, name: str, kind: str, value: dict) -> Context: - self._validate_key(key) - self._validate_key(kind) +# def context(self, key: str, name: str, kind: str, value: dict) -> Context: +# self._validate_key(key) +# self._validate_key(kind) - context = Context.builder(key).name(name) +# context = Context.builder(key).name(name) - for x in value: - context = context.set(x, value[x]) +# for x in value: +# context = context.set(x, value[x]) - return context.kind(kind).build() +# return context.kind(kind).build() - def join_contexts(self, *contexts: Context): - return Context.create_multi(*contexts) +# def join_contexts(self, *contexts: Context): +# return Context.create_multi(*contexts) diff --git a/postgresql-hook.bash b/postgresql-hook.bash old mode 100644 new mode 100755