From de22fcfa08c6039d87fa56e89ef66cee9f44e883 Mon Sep 17 00:00:00 2001 From: Nischay Date: Sat, 14 Dec 2024 23:24:19 +0530 Subject: [PATCH 1/7] feat: Added comprehensive test coverage for Group-related models (#1020) * feat: Added tests for Group model Added following tests for Group model: - Group Creation: Tests successful creation, missing required fields, and invalid data. - Group Update: Tests successful update and handling of invalid input. - Group Deletion: Tests successful deletion and an attempt to delete a non-existent group. * test(group): Add comprehensive Group model validation tests - Add test_group_creation to validate complete Group object creation - Add test_required_fields to ensure validation for mandatory fields - Add test_optional_fields to verify handling of optional fields This commit introduces three unit tests for the Group model: 1. Validates full object creation with all fields 2. Checks validation errors for missing required fields 3. Confirms optional fields can be blank/null * test: add Group model validation and cascade delete tests Add tests to verify: - Maximum length constraints for Group model fields - Cascade deletion behavior when parent Organization or User is deleted * test: Add comprehensive test coverage for Group models - Add test cases for GroupMember model covering roles and membership - Add test cases for GroupResource model with creation and deletion flows - Add test cases for GroupText model including language handling - Add test cases for GroupTopic model with relationship validations - Add test cases for GroupEvent model with multiple event relation - Expand Group model tests with field validations and cascade deletions The test suite now validates: - Model relationships and constraints - Field validations and max lengths - Multi-language support for text content - Cascade deletion behavior - Required vs optional field handling * fix error: Function is missing a return type annotation [no-untyped-def] * Modified comments in files to follow Python standard, ensuring punctuation and capitalization. --- .vscode/settings.json | 7 +- backend/entities/tests.py | 3 +- backend/entities/tests/test_group.py | 235 ++++++++++++++++++ backend/entities/tests/test_group_events.py | 25 ++ backend/entities/tests/test_group_member.py | 57 +++++ backend/entities/tests/test_group_resource.py | 50 ++++ backend/entities/tests/test_group_text.py | 63 +++++ backend/entities/tests/test_group_topic.py | 50 ++++ 8 files changed, 487 insertions(+), 3 deletions(-) create mode 100644 backend/entities/tests/test_group.py create mode 100644 backend/entities/tests/test_group_events.py create mode 100644 backend/entities/tests/test_group_member.py create mode 100644 backend/entities/tests/test_group_resource.py create mode 100644 backend/entities/tests/test_group_text.py create mode 100644 backend/entities/tests/test_group_topic.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 2e37f3f36..aa5ef6aa2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,10 @@ { "eslint.validate": ["javascript", "typescript", "vue"], "eslint.useFlatConfig": true, - "typescript.tsdk": "./frontend/node_modules/typescript/lib" + "typescript.tsdk": "./frontend/node_modules/typescript/lib", + "python.testing.pytestArgs": [ + "backend" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true } diff --git a/backend/entities/tests.py b/backend/entities/tests.py index 70e012b75..a872fe067 100644 --- a/backend/entities/tests.py +++ b/backend/entities/tests.py @@ -7,8 +7,6 @@ from .factories import ( OrganizationFactory, - OrganizationApplicationFactory, - # OrganizationApplicationStatusFactory, OrganizationEventFactory, OrganizationMemberFactory, OrganizationResourceFactory, @@ -25,6 +23,7 @@ def test_str_methods() -> None: + """Test the __str__ methods of the entities.""" organization = OrganizationFactory.create() # Note: Needs to be updated to reflect the recent changes. # organization_application = OrganizationApplicationFactory.create() diff --git a/backend/entities/tests/test_group.py b/backend/entities/tests/test_group.py new file mode 100644 index 000000000..2ddcab3e2 --- /dev/null +++ b/backend/entities/tests/test_group.py @@ -0,0 +1,235 @@ +""" +Testing for the Group model. +""" + +# mypy: ignore-errors +from datetime import datetime +from uuid import UUID + +import pytest +from django.core.exceptions import ValidationError +from faker import Faker + +from authentication.factories import UserFactory +from entities.factories import ( + GroupFactory, + OrganizationFactory, +) +from entities.models import Group + +pytestmark = pytest.mark.django_db + + +def test_group_creation() -> None: + """Test complete group creation with all fields.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + fake = Faker() + + group = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + tagline=fake.catch_phrase(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + assert isinstance(group.id, UUID) + assert group.org_id == org + assert group.created_by == user + assert isinstance(group.group_name, str) + assert isinstance(group.creation_date, datetime) + assert group.terms_checked is True + + +def test_required_fields() -> None: + """Test that required fields raise validation error when missing.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + + # 1. Test missing group_name. + with pytest.raises(ValidationError): + group = Group( + org_id=org, + created_by=user, + name="Test Name", + location="Test Location", + category="Test Category", + ) + group.full_clean() + + # 2. Test missing location. + with pytest.raises(ValidationError): + group = Group( + org_id=org, + created_by=user, + group_name="Test Group", + name="Test Name", + category="Test Category", + ) + group.full_clean() + + +def test_optional_fields() -> None: + """Test that optional fields can be blank or null.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + + group = Group.objects.create( + org_id=org, + created_by=user, + group_name="Test Group", + name="Test Name", + location="Test Location", + category="Test Category", + ) + + # Should not raise ValidationError. + group.full_clean() + assert group.tagline == "" + assert group.get_involved_url == "" + assert group.terms_checked is False + + +def test_field_max_lengths() -> None: + """Test that fields have correct max lengths.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + fake = Faker() + + group = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + tagline=fake.catch_phrase(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + assert len(group.group_name) <= 100 + assert len(group.name) <= 100 + assert len(group.tagline) <= 200 + assert len(group.location) <= 100 + assert len(group.category) <= 100 + assert len(group.get_involved_url) <= 200 + + +def test_cascade_delete() -> None: + """Test that deleting an organization deletes all associated groups.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + + GroupFactory(org_id=org, created_by=user) + + assert Group.objects.count() == 1 + org.delete() + assert Group.objects.count() == 0 + + org = OrganizationFactory(created_by=user) + GroupFactory(org_id=org, created_by=user) + + assert Group.objects.count() == 1 + user.delete() + assert Group.objects.count() == 0 + + +def test_url_validations() -> None: + """Test that get_involved_url field is a valid URL.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + fake = Faker() + + # 1. Test invalid URL. + with pytest.raises(ValidationError): + group = Group( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + location=fake.city(), + category=fake.word(), + get_involved_url="not a url", + terms_checked=True, + ) + group.full_clean() + + # 2. Test valid URL. + group = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + group.full_clean() + + +def test_auto_fields() -> None: + """Test that auto fields are set correctly.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + fake = Faker() + + group = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + assert group.creation_date is not None + assert isinstance(group.creation_date, datetime) + assert group.id is not None + assert isinstance(group.id, UUID) + + +def test_multiple_groups_per_org() -> None: + """Test that multiple groups can be created per organization.""" + user = UserFactory() + org = OrganizationFactory(created_by=user) + fake = Faker() + + group1 = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + group2 = Group.objects.create( + org_id=org, + created_by=user, + group_name=fake.company(), + name=fake.company(), + location=fake.city(), + category=fake.word(), + get_involved_url=fake.url(), + terms_checked=True, + ) + + assert Group.objects.count() == 2 + assert group1.org_id == org + assert group2.org_id == org + + org_groups = Group.objects.filter(org_id=org) + assert group1 in org_groups + assert group2 in org_groups diff --git a/backend/entities/tests/test_group_events.py b/backend/entities/tests/test_group_events.py new file mode 100644 index 000000000..48cc19a9f --- /dev/null +++ b/backend/entities/tests/test_group_events.py @@ -0,0 +1,25 @@ +""" +Test cases for the GroupEvents entity. +""" + +import pytest + +from entities.factories import GroupEventFactory, GroupFactory + +pytestmark = pytest.mark.django_db + + +def test_group_event_str() -> None: + """Test string representation of GroupEvent model.""" + group_event = GroupEventFactory.build() + assert str(group_event) == f"{group_event.id}" + + +def test_multiple_events_per_group() -> None: + """Test multiple events for a single group.""" + group = GroupFactory() + events = [GroupEventFactory(group_id=group) for _ in range(3)] + + assert len(events) == 3 + for event in events: + assert event.group_id == group diff --git a/backend/entities/tests/test_group_member.py b/backend/entities/tests/test_group_member.py new file mode 100644 index 000000000..830931326 --- /dev/null +++ b/backend/entities/tests/test_group_member.py @@ -0,0 +1,57 @@ +""" +Test cases for the GroupMember model. +""" + +import pytest + +from authentication.factories import UserFactory +from entities.factories import GroupFactory, GroupMemberFactory + +pytestmark = pytest.mark.django_db + + +def test_group_member_str() -> None: + """Test string representation of GroupMember model.""" + group_member = GroupMemberFactory.build() + assert str(group_member) == f"{group_member.id}" + + +def test_group_member_roles() -> None: + """Test the different roles a group member can have.""" + user = UserFactory() + group = GroupFactory() + + # 1. Test owner role. + owner = GroupMemberFactory( + group_id=group, user_id=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 = GroupMemberFactory( + group_id=group, user_id=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 = GroupMemberFactory( + group_id=group, user_id=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_group() -> None: + """Test multiple members in a single group.""" + group = GroupFactory() + members = [GroupMemberFactory(group_id=group) for _ in range(3)] + + assert len(members) == 3 + + for member in members: + assert member.group_id == group diff --git a/backend/entities/tests/test_group_resource.py b/backend/entities/tests/test_group_resource.py new file mode 100644 index 000000000..45b43859e --- /dev/null +++ b/backend/entities/tests/test_group_resource.py @@ -0,0 +1,50 @@ +""" +Test cases for GroupResource model. +""" + +import pytest + +from entities.factories import GroupFactory, GroupResourceFactory +from entities.models import GroupResource + +pytestmark = pytest.mark.django_db + + +def test_group_resource_str() -> None: + """Test string representation of GroupResource model.""" + group_resource = GroupResourceFactory.build() + assert str(group_resource) == f"{group_resource.id}" + + +def test_group_resource_creation() -> None: + """Test creating a GroupResource instance.""" + group = GroupFactory() + resource = GroupResourceFactory(group_id=group) + + assert isinstance(resource, GroupResource) + assert resource.group_id == group + + +def test_multiple_resources_per_group() -> None: + """Test multiple resources for a single group.""" + group = GroupFactory() + resources = [GroupResourceFactory(group_id=group) for _ in range(3)] + + assert len(resources) == 3 + for resource in resources: + assert resource.group_id == group + + +def test_group_resource_deletion() -> None: + """Test cascade deletion when group is deleted.""" + group = GroupFactory() + resource = GroupResourceFactory(group_id=group) + + # Store resource ID for later verification. + resource_id = resource.id + + # Delete the group. + group.delete() + + # Verify resource is also deleted. + assert not GroupResource.objects.filter(id=resource_id).exists() diff --git a/backend/entities/tests/test_group_text.py b/backend/entities/tests/test_group_text.py new file mode 100644 index 000000000..df958a5a2 --- /dev/null +++ b/backend/entities/tests/test_group_text.py @@ -0,0 +1,63 @@ +""" +Test cases for the GroupText model. +""" + +import pytest + +from entities.factories import GroupFactory, GroupTextFactory + +pytestmark = pytest.mark.django_db + + +def test_group_text_str() -> None: + """Test string representation of GroupText model.""" + group_text = GroupTextFactory.build() + assert hasattr(group_text, "description") + + +def test_group_text_languages() -> None: + """Test group text with different ISO languages.""" + group = GroupFactory() + + # 1. Test primary language text. + primary_text = GroupTextFactory( + group_id=group, + 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 = GroupTextFactory( + group_id=group, + iso="spa", + primary=False, + description="Descripción", + get_involved="Cómo participar", + donate_prompt="Prompt de donación", + ) + assert secondary_text.primary is False + assert secondary_text.iso == "spa" + assert secondary_text.description == "Descripción" + + +def test_group_text_field_lengths() -> None: + """Test field length constraints.""" + group = GroupFactory() + + text = GroupTextFactory( + group_id=group, + iso="eng", + description="A" * 500, + get_involved="B" * 500, + donate_prompt="C" * 500, + ) + assert len(text.description) <= 500 + assert len(text.get_involved) <= 500 + assert len(text.donate_prompt) <= 500 + assert len(text.iso) <= 3 diff --git a/backend/entities/tests/test_group_topic.py b/backend/entities/tests/test_group_topic.py new file mode 100644 index 000000000..6d68520c2 --- /dev/null +++ b/backend/entities/tests/test_group_topic.py @@ -0,0 +1,50 @@ +""" +Test cases for GroupTopic model. +""" + +import pytest + +from entities.factories import GroupFactory, GroupTopicFactory +from entities.models import GroupTopic + +pytestmark = pytest.mark.django_db + + +def test_group_topic_str() -> None: + """Test string representation of GroupTopic model.""" + group_topic = GroupTopicFactory.build() + assert str(group_topic) == f"{group_topic.id}" + + +def test_group_topic_creation() -> None: + """Test creating a GroupTopic instance.""" + group = GroupFactory() + topic = GroupTopicFactory(group_id=group) + + assert isinstance(topic, GroupTopic) + assert topic.group_id == group + + +def test_multiple_topics_per_group() -> None: + """Test multiple topics for a single group.""" + group = GroupFactory() + topics = [GroupTopicFactory(group_id=group) for _ in range(3)] + + assert len(topics) == 3 + for topic in topics: + assert topic.group_id == group + + +def test_group_topic_deletion() -> None: + """Test cascade deletion when group is deleted.""" + group = GroupFactory() + topic = GroupTopicFactory(group_id=group) + + # Store topic ID for later verification. + topic_id = topic.id + + # Delete the group. + group.delete() + + # Verify topic is also deleted. + assert not GroupTopic.objects.filter(id=topic_id).exists() From e1f45f22ae3845173ff4ebd61f9efa62937c4581 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Mon, 16 Dec 2024 23:20:27 +0100 Subject: [PATCH 2/7] Translated using Weblate (Spanish) (#1067) Currently translated at 92.4% (511 of 553 strings) Translation: activist/activist Translate-URL: https://hosted.weblate.org/projects/activist/activist/es/ Co-authored-by: gallegonovato --- frontend/i18n/es.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/i18n/es.json b/frontend/i18n/es.json index 7f574b95f..cc3c75cc7 100644 --- a/frontend/i18n/es.json +++ b/frontend/i18n/es.json @@ -115,12 +115,12 @@ "components.card_details.header": "Detalles", "components.card_discussion.filter_discussion_category_aria_label": "Filtrar por el tipo de discusión", "components.card_discussion_entry.on": "encender", - "components.card_discussion_input.comment": "Comentar", + "components.card_discussion_input.comment": "Comentario", "components.card_discussion_input.comment_aria_label": "Publicar el comentario a la discusión", "components.card_discussion_input.leave_comment": "Dejar un comentario público", "components.card_discussion_input.leave_comment_high_risk": "Eres un miembro de una organización de alta riesgo. Ten cuidado lo que escribes por favor.", "components.card_discussion_input.markdown_support": "Este editor admite Markdown", - "components.card_discussion_input.preview": "Preestrenar", + "components.card_discussion_input.preview": "Vista previa", "components.card_discussion_input.preview_aria_label": "Obtener una vista previa del texto que se enviará", "components.card_discussion_input.write": "Escribir", "components.card_discussion_input.write_aria_label": "Cambiar a escritura en el campo de texto", @@ -138,7 +138,7 @@ "components.card_metrics_overview.header": "Actividad reciente", "components.card_metrics_overview.learn": "Aprender", "components.card_metrics_overview.new_organizations": "Org nueva", - "components.card_org_application_vote.downvote_application_aria_label": "Vota para no apoyar a la organización que se une a activist", + "components.card_org_application_vote.downvote_application_aria_label": "Vota para no apoyar a la organización que se suma al movimiento", "components.card_search_result.event_img_alt_text": "El logotipo del evento de", "components.card_search_result.label": "estrellas", "components.card_search_result.members": "Miembros", @@ -277,7 +277,7 @@ "components.modal_qr_code.section_1_paragraph_1_resource": "Este es el código QR único para esta página que permitirá a las personas descubrir fácilmente más sobre este recurso en la vida real.", "components.modal_qr_code.section_1_paragraph_1_user": "Este es el código QR único para esta página que permitirá a las personas descubrir fácilmente más sobre este usuario en la vida real.", "components.modal_qr_code.section_2_list_1_item_1": "Incluyéndolos en carteles o vallas publicitarias", - "components.modal_qr_code.section_2_list_1_item_2": "Agregarlos a flyers o tarjetas", + "components.modal_qr_code.section_2_list_1_item_2": "Agregar a folletos o tarjetas", "components.modal_qr_code.section_2_list_1_item_3": "Enviándolos con materiales para su organización", "components.modal_qr_code.subheader_2": "Ideas para el uso del código QR incluye:", "components.modal_qr_code.tooltip": "Abra el código QR en una nueva pestaña", From 68afc7451593e3d3af5574997407da58fdab2275 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Wed, 18 Dec 2024 19:59:39 +0100 Subject: [PATCH 3/7] Translated using Weblate (Spanish) (#1069) Currently translated at 100.0% (553 of 553 strings) Translation: activist/activist Translate-URL: https://hosted.weblate.org/projects/activist/activist/es/ Co-authored-by: gallegonovato --- frontend/i18n/es.json | 45 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/frontend/i18n/es.json b/frontend/i18n/es.json index cc3c75cc7..7bd642b3f 100644 --- a/frontend/i18n/es.json +++ b/frontend/i18n/es.json @@ -326,7 +326,7 @@ "components.sidebar_left_header.sidebar_collapse_aria_label": "Expandir o contraer la barra lateral izquierda", "components.sidebar_right_hamburger.collapse_aria_label": "Expandir o contraer la barra lateral derecha", "components.tooltip_discussion_warning.email_addresses": "Correos electrónicos", - "components.tooltip_discussion_warning.home_addresses": "Direcciones de domicilio", + "components.tooltip_discussion_warning.home_addresses": "Tu dirección postal", "components.tooltip_discussion_warning.names": "Nombres", "components.tooltip_discussion_warning.phone_numbers": "Números de teléfono", "components.tooltip_discussion_warning.text": "Por favor, haga referencia a las personas con nombres de usuario y NO escriba ninguna información personal, incluyendo:", @@ -478,9 +478,9 @@ "pages.index.learn_more": "Aprende más", "pages.index.our_supporters": "Nuestros seguidores", "pages.index.our_supporters_btn_become_aria_label": "Conviértete en un partidario del activista", - "pages.index.our_supporters_btn_view_aria_label": "Ver todos los patricionadores del activista", + "pages.index.our_supporters_btn_view_aria_label": "Ver todos los seguidores del activista", "pages.index.our_supporters_sub_text": "El patrocinio no es igual a influencia. activist siempre será gobernado únicamente por su comunidad.", - "pages.index.our_supporters_tagline": "Patrocinadores y apoyadores de la comunidad", + "pages.index.our_supporters_tagline": "Patrocinadores y colaboradores de la comunidad", "pages.index.our_supporters_text": "Ayúdenos a crear y mantener nuestra plataforma de activismo de código abierto. Considere contribuir con sus habilidades, alcance o dinero.", "pages.index.title": "Bienvenido", "pages.index.view_all_supporters": "Ver todos los seguidores", @@ -508,11 +508,48 @@ "pages.organizations.groups.index.new_group_aria_label": "Crea un nuevo grupo", "pages.organizations.groups.index.tagline": "Nuestros grupos de trabajo", "pages.organizations.index.header_title": "Inicio: Organizaciones", + "pages.organizations.index.subheader": "Organizaciones activas cerca de ti", + "pages.organizations.search.header_title": "Buscar organizaciones", + "pages.organizations.search.subheader": "Encontrar organizaciones a las que apoyar", + "pages.organizations.settings.danger_zone_delete_organization_cta": "Eliminar permanentemente la organización", + "pages.organizations.settings.danger_zone_delete_organization_cta_aria_label": "Eliminar permanentemente la organización a través de su configuración", + "pages.organizations.settings.danger_zone_delete_organization_text": "Aquí puedes eliminar tu cuenta. Por favor, ten en cuenta que esta acción no es reversible: todos los permisos y configuraciones que hayas guardado se perderán de forma permanente. Si tiene preguntas sobre su cuenta, póngase en contacto con nosotros en nuestra página de contacto.", + "pages.organizations.team.invite_someone_org_aria_label": "Invitar a unirse a esta organización", + "pages.organizations.team.tagline": "Personas involucradas", + "pages.resources.create.complete_application_aria_label": "Vincula el recurso para guardarlo y compartirlo con otros", "pages.resources.create.create_resource": "Crear recurso", + "pages.resources.create.description": "Descripción breve", + "pages.resources.create.description_placeholder": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua", + "pages.resources.create.link_placeholder": "Una URL al recurso", + "pages.resources.create.location_placeholder": "Una ubicación si el recurso corresponde a un lugar determinado", + "pages.resources.create.organization_placeholder": "Seleccione una organización si el recurso debe estar vinculado a ella", + "pages.resources.create.resource_name_placeholder": "Nombre del recurso", + "pages.resources.create.subtext": "¡Gracias por enlazar un recurso a activist! Así puedes tener todas las herramientas y la información que necesitas para ser activo en un solo lugar. Por favor, sólo enlaza recursos que tengas derecho a compartir con otros. Puedes obtener más información sobre nuestros términos y condiciones para materiales protegidos por derechos de autor en la página de recursos de la documentación", + "pages.resources.create.title": "Título", + "pages.resources.index.header_title": "Recursos de inicio", + "pages.resources.index.subheader": "Herramientas comunitarias populares", + "pages.search.index.header": "Búsqueda general", + "pages.search.index.subheader": "Encuentra organizaciones, eventos y más", + "types.command_palette.upcoming_events": "Próximos eventos", "types.topics.accessibility": "Accesibilidad", "types.topics.animal_rights": "Derechos de los animales", "types.topics.children_rights": "Derechos de los niños", "types.topics.democracy": "Democracia", "types.topics.education": "Educación", - "types.topics.elders": "Derechos de la tercera edad" + "types.topics.elders": "Derechos de la tercera edad", + "types.topics.emergency_relief": "Ayuda de emergencia", + "types.topics.environment": "Entorno", + "types.topics.expression": "Expresión", + "types.topics.health": "Salud", + "types.topics.housing": "Vivienda", + "types.topics.labor": "Derecho laboral", + "types.topics.lgbtqia": "LGBTQIA+", + "types.topics.migration": "Migración", + "types.topics.mobility": "Movilidad", + "types.topics.nutrition": "Nutrición", + "types.topics.peace_resolution": "Paz y resolución", + "types.topics.racial_justice": "Igualdad racial", + "types.topics.technology_privacy": "Tecnología y privacidad", + "types.topics.transparency": "Transparencia", + "types.topics.women": "Derechos de la mujer" } From a888f2c43d482fc861af583ec2a1305ffd83a726 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Wed, 18 Dec 2024 22:14:59 +0100 Subject: [PATCH 4/7] Create reset frontend testing script and link in contributing --- CONTRIBUTING.md | 19 ++++++++++--------- frontend/reset_local_env.sh | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 frontend/reset_local_env.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1057b2bc..67a1ef3f9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -629,14 +629,15 @@ All branding elements such as logos, icons, colors and fonts should follow those Nuxt uses auto imports to make frontend development more seamless, but at times these imports malfunction. For the `Property 'PROPERTY_NAME' does not exist on type...` errors, this is caused both by having an out of sync `yarn.lock` file and having Nuxt improperly installed. -Things to check are: +Please run [frontend/reset_local_env.sh](frontend/reset_local_env.sh) to reset the local frontend environment to allow for local testing. This can be done via the following commands: -1. Replace `yarn.lock` file with the one in main -2. Run `yarn cache clean` (this clears the yarn cache system wide and takes a long time) -3. Delete the `node_modules` folder -4. Load environment variables into your shell with `set -a && source ../.env.dev && set +a` -5. Rerun `yarn install` -6. Restart your IDE to assure that changes are picked up + ```bash + # Linux: + sh frontend/reset_local_env.sh -> [!NOTE] -> Also make sure that the dependencies have been installed within the appropriate directory (`/backend` and `/frontend`). + # MacOS: + sh frontend/reset_local_env.sh + + # Windows: + # Run the commands below found in frontend/reset_local_env.sh. + ``` diff --git a/frontend/reset_local_env.sh b/frontend/reset_local_env.sh new file mode 100644 index 000000000..3b09baf06 --- /dev/null +++ b/frontend/reset_local_env.sh @@ -0,0 +1,20 @@ +# Run this script to reset the local frontend environment to allow for local testing. +# macOS: sh frontend/reset_local_env.sh +# Linux: bash frontend/reset_local_env.sh +# Windows: Run the commands below. + +# Replace local yarn.lock file with the one in main: +wget -O yarn.lock https://raw.githubusercontent.com/activist-org/activist/refs/heads/main/frontend/yarn.lock + +# Clear the yarn cache system wide (might take a long time): +yarn cache clean + +# Delete the node_modules folder: +rm -rf node_modules + +# Load environment variables into your shell: +set -a && source ../.env.dev && set +a + +# Reinstall and prompt to restart IDE: +yarn install +echo "Please restart your IDE to assure that changes are picked up." From 76a72d11f9abfdd38af1f2c69bf51296785a9e2d Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Fri, 20 Dec 2024 00:40:53 +0100 Subject: [PATCH 5/7] Fix of breadcrumbs, add hydrate org events/groups, populate args --- .../management/commands/populate_db.py | 28 +++++++++++-- docker-compose.yml | 8 +++- .../card/search-result/CardSearchResult.vue | 42 +++++++++++-------- frontend/components/page/PageBreadcrumbs.vue | 7 ++-- frontend/i18n/en-US.json | 1 + frontend/pages/organizations/[id]/events.vue | 8 +--- .../pages/organizations/[id]/groups/index.vue | 3 +- frontend/stores/organization.ts | 5 +++ 8 files changed, 68 insertions(+), 34 deletions(-) diff --git a/backend/backend/management/commands/populate_db.py b/backend/backend/management/commands/populate_db.py index f4649a2b8..0aad213b5 100644 --- a/backend/backend/management/commands/populate_db.py +++ b/backend/backend/management/commands/populate_db.py @@ -33,12 +33,16 @@ def add_arguments(self, parser: ArgumentParser) -> None: parser.add_argument("--orgs-per-user", type=int, default=1) parser.add_argument("--groups-per-org", type=int, default=1) parser.add_argument("--events-per-org", type=int, default=1) + parser.add_argument("--resources-per-entity", type=int, default=1) + parser.add_argument("--faq-entries-per-entity", type=int, default=1) def handle(self, *args: str, **options: Unpack[Options]) -> None: num_users = options["users"] num_orgs_per_user = options["orgs_per_user"] num_groups_per_org = options["groups_per_org"] num_events_per_org = options["events_per_org"] + num_resources_per_entity = options["resources_per_entity"] + num_faq_entries_per_entity = options["faq_entries_per_entity"] # Clear all tables before creating new data. UserModel.objects.exclude(username="admin").delete() @@ -77,7 +81,7 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: created_by=user, ) - org_texts = OrganizationTextFactory(iso="wt", primary=True) + org_texts = OrganizationTextFactory(iso="en", primary=True) user_org = OrganizationFactory( created_by=user, @@ -100,12 +104,28 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: name=f"{user_topic.name} Group", ) + num_orgs = num_users * num_orgs_per_user + num_groups = num_users * num_orgs_per_user * num_groups_per_org + num_events = num_users * num_orgs_per_user * num_events_per_org + num_resources = num_users * ( + num_orgs_per_user + + num_orgs_per_user * num_events_per_org * num_resources_per_entity + + num_orgs_per_user * num_groups_per_org * num_resources_per_entity + ) + num_faq_entries = num_users * ( + num_orgs_per_user + + num_orgs_per_user * num_events_per_org * num_faq_entries_per_entity + + num_orgs_per_user * num_groups_per_org * num_faq_entries_per_entity + ) + self.stdout.write( self.style.ERROR( f"Number of users created: {num_users}\n" - f"Number of organizations created: {num_users * num_orgs_per_user}\n" - f"Number of groups created: {num_users * num_orgs_per_user * num_groups_per_org}\n" - f"Number of events created: {num_users * num_orgs_per_user * num_events_per_org}\n" + f"Number of organizations created: {num_orgs}\n" + f"Number of groups created: {num_groups}\n" + f"Number of events created: {num_events}\n" + f"Number of resources created: {num_resources}\n" + f"Number of FAQ entries created: {num_faq_entries}\n" ) ) diff --git a/docker-compose.yml b/docker-compose.yml index 9873891e0..2eb52c730 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,13 @@ services: python manage.py loaddata fixtures/superuser.json && python manage.py loaddata fixtures/status_types.json && python manage.py loaddata fixtures/topics.json && - python manage.py populate_db --users 10 --orgs-per-user 1 --groups-per-org 1 --events-per-org 1 && + python manage.py populate_db \ + --users 10 \ + --orgs-per-user 1 \ + --groups-per-org 1 \ + --events-per-org 1 \ + --resources-per-entity 1 \ + --faq-entries-per-entity 1 && python manage.py runserver 0.0.0.0:${BACKEND_PORT}" ports: - "${BACKEND_PORT}:${BACKEND_PORT}" diff --git a/frontend/components/card/search-result/CardSearchResult.vue b/frontend/components/card/search-result/CardSearchResult.vue index f158e004c..06f690996 100644 --- a/frontend/components/card/search-result/CardSearchResult.vue +++ b/frontend/components/card/search-result/CardSearchResult.vue @@ -34,9 +34,9 @@ }" :src="imageUrl" :alt=" - $t('components.card_search_result.organization_img_alt_text') + + $t('components.card_search_result.group_img_alt_text') + ' ' + - group.organization.name + group.name " />
- + /> -->