From 0da7a8d974255572912e389c23198fe187de6dc7 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Sun, 12 Jan 2025 18:38:09 +0100 Subject: [PATCH] Group/org updates and frontend changes to load in new data --- backend/communities/groups/factories.py | 2 - backend/communities/groups/models.py | 1 - backend/communities/groups/serializers.py | 2 +- .../communities/groups/tests/test_group.py | 1 - .../groups/tests/test_group_text.py | 8 +-- .../communities/organizations/factories.py | 8 --- backend/communities/organizations/models.py | 1 - .../organizations/tests/___init__.py | 0 .../organizations/tests/test_org_events.py | 27 ++++++++ .../organizations/tests/test_org_member.py | 62 +++++++++++++++++++ .../organizations/tests/test_org_resource.py | 35 +++++++++++ .../organizations/tests/test_org_text.py | 49 +++++++++++++++ .../organizations/tests/test_org_topic.py | 34 ++++++++++ .../core/management/commands/populate_db.py | 4 +- backend/events/factories.py | 4 +- backend/events/models.py | 4 +- backend/events/serializers.py | 2 +- frontend/components/card/CardDetails.vue | 12 ++-- .../card/search-result/CardSearchResult.vue | 47 ++++++++------ .../meta-tag/MetaTagOrganization.vue | 14 +++-- .../modal/ModalOrganizationOverview.vue | 6 +- frontend/components/modal/ModalSharePage.vue | 2 +- frontend/composables/useLinkURL.ts | 2 +- frontend/i18n/de.json | 2 +- frontend/i18n/en-US.json | 2 +- frontend/i18n/es.json | 2 +- frontend/i18n/fr.json | 2 +- frontend/i18n/pt.json | 2 +- frontend/pages/events/[id]/discussion.vue | 2 +- frontend/stores/event.ts | 16 ++++- frontend/stores/group.ts | 11 +++- frontend/types/communities/group.d.ts | 10 ++- frontend/types/communities/organization.d.ts | 1 - frontend/types/content/resource.d.ts | 2 +- frontend/types/events/event.d.ts | 10 ++- ...d_api_calls.py => check_used_api_calls.py} | 2 +- 36 files changed, 312 insertions(+), 79 deletions(-) create mode 100644 backend/communities/organizations/tests/___init__.py create mode 100644 backend/communities/organizations/tests/test_org_events.py create mode 100644 backend/communities/organizations/tests/test_org_member.py create mode 100644 backend/communities/organizations/tests/test_org_resource.py create mode 100644 backend/communities/organizations/tests/test_org_text.py create mode 100644 backend/communities/organizations/tests/test_org_topic.py rename utils/{used_api_calls.py => check_used_api_calls.py} (95%) diff --git a/backend/communities/groups/factories.py b/backend/communities/groups/factories.py index 6eaf330d6..df53d20da 100644 --- a/backend/communities/groups/factories.py +++ b/backend/communities/groups/factories.py @@ -11,8 +11,6 @@ class GroupFactory(factory.django.DjangoModelFactory): class Meta: model = Group - # Note: We should always set organizations for groups directly and not have them be generated. - # org = factory.SubFactory("communities.organizations.factories.OrganizationFactory") created_by = factory.SubFactory("authentication.factories.UserFactory") name = factory.Faker("word") tagline = factory.Faker("word") diff --git a/backend/communities/groups/models.py b/backend/communities/groups/models.py index ca786ff63..7c30997eb 100644 --- a/backend/communities/groups/models.py +++ b/backend/communities/groups/models.py @@ -13,7 +13,6 @@ class Group(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - # TODO: Remove null=True - needed for factories. org = models.ForeignKey( "communities.Organization", on_delete=models.CASCADE, diff --git a/backend/communities/groups/serializers.py b/backend/communities/groups/serializers.py index 27e713832..0a3935f94 100644 --- a/backend/communities/groups/serializers.py +++ b/backend/communities/groups/serializers.py @@ -35,8 +35,8 @@ class GroupSerializer(serializers.ModelSerializer[Group]): texts = GroupTextSerializer(many=True, read_only=True) location = LocationSerializer(read_only=True) resources = ResourceSerializer(many=True, read_only=True) - events = EventSerializer(many=True, read_only=True) org = GroupOrganizationSerializer(read_only=True) + events = EventSerializer(many=True, read_only=True) class Meta: model = Group diff --git a/backend/communities/groups/tests/test_group.py b/backend/communities/groups/tests/test_group.py index f9e304454..ec4ca8ddf 100644 --- a/backend/communities/groups/tests/test_group.py +++ b/backend/communities/groups/tests/test_group.py @@ -77,7 +77,6 @@ def test_url_validations() -> None: get_involved_url=fake.url(), terms_checked=True, ) - group.full_clean() diff --git a/backend/communities/groups/tests/test_group_text.py b/backend/communities/groups/tests/test_group_text.py index ace7921b0..8d73b9107 100644 --- a/backend/communities/groups/tests/test_group_text.py +++ b/backend/communities/groups/tests/test_group_text.py @@ -37,10 +37,10 @@ def test_group_text_languages() -> None: group=group, iso="spa", primary=False, - description="Descripción", - get_involved="Cómo participar", - donate_prompt="Prompt de donación", + description="Description", + get_involved="How to participate", + donate_prompt="Donation prompt", ) assert secondary_text.primary is False assert secondary_text.iso == "spa" - assert secondary_text.description == "Descripción" + assert secondary_text.description == "Description" diff --git a/backend/communities/organizations/factories.py b/backend/communities/organizations/factories.py index 030a1e420..633f7608c 100644 --- a/backend/communities/organizations/factories.py +++ b/backend/communities/organizations/factories.py @@ -98,11 +98,3 @@ class Meta: description = factory.Faker(provider="text", locale="la", max_nb_chars=1000) get_involved = factory.Faker(provider="text", locale="la") donate_prompt = factory.Faker(provider="text", locale="la") - - -class StatusTypeFactory(factory.django.DjangoModelFactory): - class Meta: - model = "communities.StatusType" - django_get_or_create = ("name",) - - name = "Active" diff --git a/backend/communities/organizations/models.py b/backend/communities/organizations/models.py index eda8c161a..5aa3e2fcb 100644 --- a/backend/communities/organizations/models.py +++ b/backend/communities/organizations/models.py @@ -47,7 +47,6 @@ class Organization(models.Model): faqs = models.ManyToManyField("content.Faq", blank=True) resources = models.ManyToManyField("content.Resource", blank=True) discussions = models.ManyToManyField("content.Discussion", blank=True) - events = models.ManyToManyField("events.Event", blank=True) def __str__(self) -> str: return self.name diff --git a/backend/communities/organizations/tests/___init__.py b/backend/communities/organizations/tests/___init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/backend/communities/organizations/tests/test_org_events.py b/backend/communities/organizations/tests/test_org_events.py new file mode 100644 index 000000000..7cefd5bf7 --- /dev/null +++ b/backend/communities/organizations/tests/test_org_events.py @@ -0,0 +1,27 @@ +""" +Test cases for the OrganizationEvents entity. +""" + +import pytest + +from communities.organizations.factories import OrganizationFactory +from communities.organizations.models import Organization +from events.factories import EventFactory + +pytestmark = pytest.mark.django_db + + +def test_multiple_events_per_org() -> None: + """Test multiple events for a single organization.""" + org = OrganizationFactory.create() + events = EventFactory.create_batch(3) + + org.events.set(events) + + org = Organization.objects.get(id=org.id) + org_events = org.events.all() + + assert len(events) == len(org_events) + + for event in events: + assert event in org_events diff --git a/backend/communities/organizations/tests/test_org_member.py b/backend/communities/organizations/tests/test_org_member.py new file mode 100644 index 000000000..a57f35979 --- /dev/null +++ b/backend/communities/organizations/tests/test_org_member.py @@ -0,0 +1,62 @@ +""" +Test cases for the OrganizationMember model. +""" + +import pytest + +from authentication.factories import UserFactory +from communities.organizations.factories import ( + OrganizationFactory, + OrganizationMemberFactory, +) + +pytestmark = pytest.mark.django_db + + +def test_org_member_str() -> None: + """Test string representation of OrganizationMember model.""" + org_member = OrganizationMemberFactory.build() + assert str(org_member) == f"{org_member.id}" + + +def test_org_member_roles() -> None: + """Test the different roles an organization member can have.""" + user = UserFactory() + org = OrganizationFactory() + + print(org.__dict__) + + # 1. Test owner role. + owner = OrganizationMemberFactory( + org=org, user=user, is_owner=True, is_admin=False, is_comms=False + ) + assert owner.is_owner is True + assert owner.is_admin is False + assert owner.is_comms is False + + # 2. Test admin role. + admin = OrganizationMemberFactory( + org=org, user=user, is_owner=False, is_admin=True, is_comms=False + ) + assert admin.is_owner is False + assert admin.is_admin is True + assert admin.is_comms is False + + # 3. Test comms role. + comms = OrganizationMemberFactory( + org=org, user=user, is_owner=False, is_admin=False, is_comms=True + ) + assert comms.is_owner is False + assert comms.is_admin is False + assert comms.is_comms is True + + +def test_multiple_members_per_org() -> None: + """Test multiple members in a single organization.""" + org = OrganizationFactory() + members = [OrganizationMemberFactory(org=org) for _ in range(3)] + + assert len(members) == 3 + + for member in members: + assert member.org == org diff --git a/backend/communities/organizations/tests/test_org_resource.py b/backend/communities/organizations/tests/test_org_resource.py new file mode 100644 index 000000000..f5e6270df --- /dev/null +++ b/backend/communities/organizations/tests/test_org_resource.py @@ -0,0 +1,35 @@ +""" +Test cases for OrganizationResource model. +""" + +import pytest + +from communities.organizations.factories import OrganizationFactory +from content.factories import ResourceFactory +from content.models import Resource + +pytestmark = pytest.mark.django_db + + +def test_org_resource_creation() -> None: + """Test creating a OrganizationResource instance.""" + org = OrganizationFactory() + resource = ResourceFactory() + + org.resources.set([resource]) + + assert isinstance(org.resources.first(), Resource) + assert org.resources.first() == resource + + +def test_multiple_resources_per_org() -> None: + """Test multiple resources for a single organization.""" + org = OrganizationFactory() + resources = ResourceFactory.create_batch(3) + + org.resources.set(resources) + + assert len(resources) == 3 + + for resource in resources: + assert resource in org.resources.all() diff --git a/backend/communities/organizations/tests/test_org_text.py b/backend/communities/organizations/tests/test_org_text.py new file mode 100644 index 000000000..a919ec84b --- /dev/null +++ b/backend/communities/organizations/tests/test_org_text.py @@ -0,0 +1,49 @@ +""" +Test cases for the OrganizationText model. +""" + +import pytest + +from communities.organizations.factories import ( + OrganizationFactory, + OrganizationTextFactory, +) + +pytestmark = pytest.mark.django_db + + +def test_org_text_str() -> None: + """Test string representation of OrganizationText model.""" + org_text = OrganizationTextFactory.build() + assert hasattr(org_text, "description") + + +def test_org_text_languages() -> None: + """Test organization text with different ISO languages.""" + org = OrganizationFactory() + + # 1. Test primary language text. + primary_text = OrganizationTextFactory( + org=org, + iso="eng", + primary=True, + description="Primary description", + get_involved="Get involved text", + donate_prompt="Donation prompt", + ) + assert primary_text.primary is True + assert primary_text.iso == "eng" + assert primary_text.description == "Primary description" + + # 2. Test secondary language text. + secondary_text = OrganizationTextFactory( + org=org, + iso="spa", + primary=False, + description="Description", + get_involved="How to participate", + donate_prompt="Donation prompt", + ) + assert secondary_text.primary is False + assert secondary_text.iso == "spa" + assert secondary_text.description == "Description" diff --git a/backend/communities/organizations/tests/test_org_topic.py b/backend/communities/organizations/tests/test_org_topic.py new file mode 100644 index 000000000..329b9c83f --- /dev/null +++ b/backend/communities/organizations/tests/test_org_topic.py @@ -0,0 +1,34 @@ +""" +Test cases for OrganizationTopic model. +""" + +import pytest + +from communities.organizations.factories import OrganizationFactory +from content.factories import TopicFactory +from content.models import Topic + +pytestmark = pytest.mark.django_db + + +def test_org_topic_creation() -> None: + """Test creating a OrganizationTopic instance.""" + org = OrganizationFactory() + topic = TopicFactory() + + org.topics.set([topic]) + + assert isinstance(org.topics.first(), Topic) + assert org.topics.first() == topic + + +def test_multiple_topics_per_org() -> None: + """Test multiple topics for a single organization.""" + org = OrganizationFactory() + topics = TopicFactory.create_batch(3) + + org.topics.set(topics) + + assert len(topics) == 3 + for topic in topics: + assert topic in org.topics.all() diff --git a/backend/core/management/commands/populate_db.py b/backend/core/management/commands/populate_db.py index 8fbcd3ae8..fca520d93 100644 --- a/backend/core/management/commands/populate_db.py +++ b/backend/core/management/commands/populate_db.py @@ -83,11 +83,11 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) user_org_event = EventFactory( - name=f"{user_topic.name} Event o{o}:e{e}", + name=f"{user_topic.name} Event [o{o}:e{e}]", tagline=f"{event_type_verb} {user_topic.name}", type=event_type, created_by=user, - org=user_org, + orgs=user_org, ) event_texts = EventTextFactory(iso="en", primary=True) diff --git a/backend/events/factories.py b/backend/events/factories.py index 0a3acad0e..5b539902f 100644 --- a/backend/events/factories.py +++ b/backend/events/factories.py @@ -18,9 +18,9 @@ class EventFactory(factory.django.DjangoModelFactory): class Meta: model = Event - django_get_or_create = ("created_by", "org") + django_get_or_create = ("created_by", "orgs") - org = factory.SubFactory("communities.organizations.factories.OrganizationFactory") + orgs = factory.SubFactory("communities.organizations.factories.OrganizationFactory") created_by = factory.SubFactory("authentication.factories.UserFactory") name = factory.Faker("word") tagline = factory.Faker("word") diff --git a/backend/events/models.py b/backend/events/models.py index f12fad0ef..92ea39ad3 100644 --- a/backend/events/models.py +++ b/backend/events/models.py @@ -18,8 +18,8 @@ class Event(models.Model): related_name="created_events", on_delete=models.CASCADE, ) - org = models.ForeignKey( - "communities.Organization", related_name="org", on_delete=models.CASCADE + orgs = models.ForeignKey( + "communities.Organization", related_name="events", on_delete=models.CASCADE ) name = models.CharField(max_length=255) tagline = models.CharField(max_length=255, blank=True) diff --git a/backend/events/serializers.py b/backend/events/serializers.py index 18cbcb576..ef7aaded5 100644 --- a/backend/events/serializers.py +++ b/backend/events/serializers.py @@ -35,7 +35,7 @@ class EventSerializer(serializers.ModelSerializer[Event]): texts = EventTextSerializer(many=True, read_only=True) offline_location = LocationSerializer(read_only=True) resources = ResourceSerializer(many=True, read_only=True) - org = EventOrganizationSerializer(read_only=True) + orgs = EventOrganizationSerializer(read_only=True) class Meta: model = Event diff --git a/frontend/components/card/CardDetails.vue b/frontend/components/card/CardDetails.vue index 55db0238a..fcbae363e 100644 --- a/frontend/components/card/CardDetails.vue +++ b/frontend/components/card/CardDetails.vue @@ -14,19 +14,21 @@
- + + - + /> -
-

@{{ organization.orgName }}

-

@{{ group.groupName }}

+
+ + @{{ entityName }}

(() => { if (props.organization) { - return i18n.t( - "components.card_search_result.navigate_to_organization_aria_label" - ); + return i18n.t("components._global.navigate_to_organization_aria_label"); } else if (props.group) { return i18n.t("components._global.navigate_to_group_aria_label"); } else if (props.event) { @@ -355,6 +356,16 @@ const name = computed(() => { } }); +const entityName = computed(() => { + if (props.organization) { + return props.organization.orgName; + } else if (props.group) { + return props.group.groupName; + } else { + return ""; + } +}); + const onlineLocation = computed(() => { if (props.event && props.event.onlineLocationLink) { return props.event.onlineLocationLink; @@ -363,17 +374,15 @@ const onlineLocation = computed(() => { } }); -// const organizations = computed(() => { -// if (props.group) { -// return [props.group.organization]; -// } else if (props.event) { -// return props.event.organizations; -// } else if (props.resource) { -// return [props.resource.organization]; -// } else { -// return []; -// } -// }); +const organizations = computed(() => { + if (props.event) { + return [props.event.orgs]; + } else if (props.resource) { + return [props.resource.org]; + } else { + return []; + } +}); // const stars = computed(() => { // if (props.resource && props.resource.starers) { diff --git a/frontend/components/meta-tag/MetaTagOrganization.vue b/frontend/components/meta-tag/MetaTagOrganization.vue index f46c14c95..78a27dfc9 100644 --- a/frontend/components/meta-tag/MetaTagOrganization.vue +++ b/frontend/components/meta-tag/MetaTagOrganization.vue @@ -1,9 +1,13 @@