From 30806d37ca01f9bdf10f64c3c16266c159a5be2b Mon Sep 17 00:00:00 2001 From: Matt Burnett Date: Sat, 7 Dec 2024 16:00:09 +0100 Subject: [PATCH 01/14] Local commit: Partial commit to share with Andrew while we debug 404 error in org store addSocialLink method --- frontend/components/card/CardConnect.vue | 2 +- frontend/stores/organization.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index d068fcc2a..eff963d56 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -124,7 +124,7 @@ const props = defineProps<{ pageType: "organization" | "group" | "event" | "other"; }>(); -// TODO: uncomment and delete 'true' line after issue 1006 is done. +// TODO: restore after 1006 is resolved. // const { userIsSignedIn } = useUser(); const userIsSignedIn = true; const paramsId = useRoute().params.id; diff --git a/frontend/stores/organization.ts b/frontend/stores/organization.ts index dd7c47d21..c09f11368 100644 --- a/frontend/stores/organization.ts +++ b/frontend/stores/organization.ts @@ -229,7 +229,7 @@ export const useOrganizationStore = defineStore("organization", { const token = localStorage.getItem("accessToken"); const responseSocialLinks = await useFetch( - `${BASE_BACKEND_URL as string}/content/social_links/${org.id}/`, + `${BASE_BACKEND_URL as string}/content/social_links/${org.id}`, { method: "PUT", body: JSON.stringify({ @@ -249,7 +249,6 @@ export const useOrganizationStore = defineStore("organization", { if (responseSocialLinksData) { this.loading = false; - // return responseSocialLinksData.id; return responseSocialLinksData; } From 84ed05152f1721ed1ff9ca2263b8269ee5613040 Mon Sep 17 00:00:00 2001 From: Matt Burnett Date: Sat, 7 Dec 2024 16:01:34 +0100 Subject: [PATCH 02/14] Local commit: Added Popup file. --- frontend/components/popup/PopupNewField.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/components/popup/PopupNewField.vue b/frontend/components/popup/PopupNewField.vue index de0982e2b..743053405 100644 --- a/frontend/components/popup/PopupNewField.vue +++ b/frontend/components/popup/PopupNewField.vue @@ -82,6 +82,7 @@ const inputLabel = ref(""); const handleAddClick = () => { // Validate user input and emit 'add' event + payload to CardConnect.vue. // CardConnect.vue handles the data PUT's via the org store. + // if (!inputValue.value.trim()) { alert("Please enter a 'Link to account'."); From 5bb2947f36a5b2e10e1d7c216f17363997ad84dc Mon Sep 17 00:00:00 2001 From: Matt Burnett Date: Sat, 7 Dec 2024 16:26:39 +0100 Subject: [PATCH 03/14] Local commit: Restored files from PR 1037. CardConnect.vue PopupNewField.vue organization.ts types/social-links-payload.ts --- frontend/components/card/CardConnect.vue | 33 ++++++++++++++++----- frontend/components/popup/PopupNewField.vue | 19 +++++------- frontend/stores/organization.ts | 10 ++++--- frontend/types/social-links-payload.ts | 4 +++ 4 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 frontend/types/social-links-payload.ts diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 4ab6848b3..d068fcc2a 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -60,6 +60,7 @@ }" > + + (); -// TODO: restore after 1006 is resolved. +// TODO: uncomment and delete 'true' line after issue 1006 is done. // const { userIsSignedIn } = useUser(); const userIsSignedIn = true; - const paramsId = useRoute().params.id; const paramsIdGroup = useRoute().params.groupId; @@ -161,13 +165,26 @@ const socialLinksRef = computed(() => { } }); -const toggleEditMode = () => { - editModeEnabled.value = !editModeEnabled.value; -}; +const handlePopupAddClick = async (payload: AddPayload, close: () => void) => { + // Put the social links payload in the database. + // TODO: Needs more robust handling (ie try/catch + error trapping/handling) + const response = await organizationStore.addSocialLinks( + organization, + payload + ); + if (response) { + console.log("org store addSocialLinks response: " + response); + console.log( + "CardConnect addSocialLinks payload: " + JSON.stringify(payload) + ); + } -const onClose = (close: (ref?: HTMLElement) => void) => { close(); }; +const toggleEditMode = () => { + editModeEnabled.value = !editModeEnabled.value; +}; + const emit = defineEmits(["on-new-account", "on-account-removed"]); diff --git a/frontend/components/popup/PopupNewField.vue b/frontend/components/popup/PopupNewField.vue index 7e9710424..de0982e2b 100644 --- a/frontend/components/popup/PopupNewField.vue +++ b/frontend/components/popup/PopupNewField.vue @@ -23,6 +23,7 @@ id="popup-input" class="focus-brand h-8 w-52 rounded-sm border border-primary-text bg-transparent p-2" type="text" + required :placeholder="fieldNamePrompt" /> @@ -43,14 +45,11 @@ cols="10" :placeholder="descriptionPrompt" > -
+
+ (); -const inputValue = ref(null); -const inputLabel = ref(null); +const emit = defineEmits(["add-clicked", "on-close-clicked"]); const inputValue = ref(""); const inputLabel = ref(""); @@ -84,7 +82,6 @@ const inputLabel = ref(""); const handleAddClick = () => { // Validate user input and emit 'add' event + payload to CardConnect.vue. // CardConnect.vue handles the data PUT's via the org store. - // if (!inputValue.value.trim()) { alert("Please enter a 'Link to account'."); @@ -110,6 +107,4 @@ const handleAddClick = () => { label: inputLabel.value, }); }; - -const emit = defineEmits(["on-cta-clicked", "on-close-clicked"]); diff --git a/frontend/stores/organization.ts b/frontend/stores/organization.ts index 92e390031..dd7c47d21 100644 --- a/frontend/stores/organization.ts +++ b/frontend/stores/organization.ts @@ -5,6 +5,7 @@ import type { PiniaResOrganization, PiniaResOrganizations, } from "~/types/entities/organization"; +import type { AddPayload } from "~/types/social-links-payload"; interface OrganizationStore { loading: boolean; @@ -56,7 +57,7 @@ export const useOrganizationStore = defineStore("organization", { const token = localStorage.getItem("accessToken"); const responseOrg = await useFetch( - `${BASE_BACKEND_URL}/entities/organizations/`, + `${BASE_BACKEND_URL as string}/entities/organizations/`, { method: "POST", body: JSON.stringify({ @@ -170,7 +171,7 @@ export const useOrganizationStore = defineStore("organization", { const token = localStorage.getItem("accessToken"); const responseOrg = await $fetch( - BASE_BACKEND_URL + `/entities/organizations/${org.id}/`, + (BASE_BACKEND_URL as string) + `/entities/organizations/${org.id}/`, { method: "PUT", body: { @@ -184,7 +185,7 @@ export const useOrganizationStore = defineStore("organization", { ); const responseOrgTexts = await $fetch( - BASE_BACKEND_URL + + (BASE_BACKEND_URL as string) + `/entities/organization_texts/${org.organizationTextId}/`, { method: "PUT", @@ -228,7 +229,7 @@ export const useOrganizationStore = defineStore("organization", { const token = localStorage.getItem("accessToken"); const responseSocialLinks = await useFetch( - `${BASE_BACKEND_URL as string}/content/social_links/${org.id}`, + `${BASE_BACKEND_URL as string}/content/social_links/${org.id}/`, { method: "PUT", body: JSON.stringify({ @@ -248,6 +249,7 @@ export const useOrganizationStore = defineStore("organization", { if (responseSocialLinksData) { this.loading = false; + // return responseSocialLinksData.id; return responseSocialLinksData; } diff --git a/frontend/types/social-links-payload.ts b/frontend/types/social-links-payload.ts new file mode 100644 index 000000000..452c88470 --- /dev/null +++ b/frontend/types/social-links-payload.ts @@ -0,0 +1,4 @@ +export interface AddPayload { + link: string; + label: string; +} From ec9427147289de057d5e457a8b7d317cd1155f98 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Sun, 19 Jan 2025 00:41:33 +0100 Subject: [PATCH 04/14] Fix Prettier issues on card connect --- frontend/components/card/CardConnect.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 513d08f92..412c7a929 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -87,7 +87,9 @@ (payload: AddPayload) => handlePopupAddClick(payload, close) " @on-close-clicked="close" - :title="$t(i18nMap.components.card_connect.app_account_popup_title)" + :title=" + $t(i18nMap.components.card_connect.app_account_popup_title) + " :fieldNamePrompt=" $t( i18nMap.components.card_connect @@ -123,9 +125,9 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue"; import type { Group } from "~/types/communities/group"; import type { Organization } from "~/types/communities/organization"; import type { Event } from "~/types/events/event"; -import type { AddPayload } from "~/types/social-links-payload"; import { i18nMap } from "~/types/i18n-map"; import { IconMap } from "~/types/icon-map"; +import type { AddPayload } from "~/types/social-links-payload"; const props = defineProps<{ pageType: "organization" | "group" | "event" | "other"; From 8ece7c9be7bd42bf44701f6e3dd74436613a4287 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Sun, 19 Jan 2025 12:52:41 +0100 Subject: [PATCH 05/14] Split social links model into entity based approach similar to texts --- backend/communities/groups/factories.py | 24 +++++++++- backend/communities/groups/models.py | 18 +++++++- backend/communities/groups/serializers.py | 8 ++++ .../communities/organizations/factories.py | 17 +++++++ backend/communities/organizations/models.py | 18 +++++++- .../communities/organizations/serializers.py | 10 ++++ .../core/management/commands/populate_db.py | 33 +++++++++++-- backend/events/factories.py | 16 +++++++ backend/events/models.py | 17 ++++++- backend/events/serializers.py | 9 +++- frontend/components/card/CardConnect.vue | 46 +++++++++++++------ frontend/stores/event.ts | 2 +- frontend/stores/group.ts | 2 +- frontend/stores/organization.ts | 6 +-- frontend/types/communities/group.d.ts | 7 ++- frontend/types/communities/organization.d.ts | 7 ++- frontend/types/content/social-link.d.ts | 8 ++++ frontend/types/events/event.d.ts | 7 ++- 18 files changed, 225 insertions(+), 30 deletions(-) create mode 100644 frontend/types/content/social-link.d.ts diff --git a/backend/communities/groups/factories.py b/backend/communities/groups/factories.py index 0dd8453e4..24b8ca6c1 100644 --- a/backend/communities/groups/factories.py +++ b/backend/communities/groups/factories.py @@ -1,9 +1,16 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import datetime +import random import factory -from communities.groups.models import Group, GroupImage, GroupMember, GroupText +from communities.groups.models import ( + Group, + GroupImage, + GroupMember, + GroupSocialLink, + GroupText, +) # MARK: Main Tables @@ -43,6 +50,21 @@ class Meta: is_admin = factory.Faker("boolean") +class GroupSocialLinkFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupSocialLink + + link = "https://www.activist.org" + label = "social link" + order = random.randint(0, 10) + creation_date = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + last_updated = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + + class GroupTextFactory(factory.django.DjangoModelFactory): class Meta: model = GroupText diff --git a/backend/communities/groups/models.py b/backend/communities/groups/models.py index 9f43dec37..955346e9a 100644 --- a/backend/communities/groups/models.py +++ b/backend/communities/groups/models.py @@ -32,7 +32,6 @@ class Group(models.Model): terms_checked = models.BooleanField(default=False) creation_date = models.DateTimeField(auto_now_add=True) - social_links = models.ManyToManyField("content.SocialLink", blank=True) topics = models.ManyToManyField("content.Topic", blank=True) faqs = models.ManyToManyField("content.Faq", blank=True) events = models.ManyToManyField("events.Event", blank=True) @@ -71,6 +70,20 @@ def __str__(self) -> str: return f"{self.id}" +class GroupSocialLink(models.Model): + group = models.ForeignKey( + Group, on_delete=models.CASCADE, null=True, related_name="social_links" + ) + link = models.CharField(max_length=255) + label = models.CharField(max_length=255) + order = models.IntegerField() + creation_date = models.DateTimeField(auto_now_add=True) + last_updated = models.DateTimeField(auto_now=True) + + def __str__(self) -> str: + return self.label + + class GroupText(models.Model): group = models.ForeignKey( Group, on_delete=models.CASCADE, null=True, related_name="texts" @@ -80,3 +93,6 @@ class GroupText(models.Model): description = models.TextField(max_length=500) get_involved = models.TextField(max_length=500, blank=True) donate_prompt = models.TextField(max_length=500, blank=True) + + def __str__(self) -> str: + return f"{self.group} - {self.iso}" diff --git a/backend/communities/groups/serializers.py b/backend/communities/groups/serializers.py index 16d8b8d94..0eb3491e9 100644 --- a/backend/communities/groups/serializers.py +++ b/backend/communities/groups/serializers.py @@ -11,6 +11,7 @@ Group, GroupImage, GroupMember, + GroupSocialLink, GroupText, ) from communities.organizations.models import Organization @@ -20,6 +21,12 @@ # MARK: Main Tables +class GroupSocialLinkSerializer(serializers.ModelSerializer[GroupSocialLink]): + class Meta: + model = GroupSocialLink + fields = "__all__" + + class GroupTextSerializer(serializers.ModelSerializer[GroupText]): class Meta: model = GroupText @@ -34,6 +41,7 @@ class Meta: class GroupSerializer(serializers.ModelSerializer[Group]): texts = GroupTextSerializer(many=True, read_only=True) + social_links = GroupSocialLinkSerializer(many=True, read_only=True) location = LocationSerializer(read_only=True) resources = ResourceSerializer(many=True, read_only=True) org = GroupOrganizationSerializer(read_only=True) diff --git a/backend/communities/organizations/factories.py b/backend/communities/organizations/factories.py index 650d0b27f..2b85a6b94 100644 --- a/backend/communities/organizations/factories.py +++ b/backend/communities/organizations/factories.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import datetime +import random import factory @@ -9,6 +10,7 @@ OrganizationApplicationStatus, OrganizationImage, OrganizationMember, + OrganizationSocialLink, OrganizationTask, OrganizationText, ) @@ -81,6 +83,21 @@ class Meta: is_comms = factory.Faker("boolean") +class OrganizationSocialLinkFactory(factory.django.DjangoModelFactory): + class Meta: + model = OrganizationSocialLink + + link = "https://www.activist.org" + label = "social link" + order = random.randint(0, 10) + creation_date = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + last_updated = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + + class OrganizationTaskFactory(factory.django.DjangoModelFactory): class Meta: model = OrganizationTask diff --git a/backend/communities/organizations/models.py b/backend/communities/organizations/models.py index ab20eb31d..e3f0948a4 100644 --- a/backend/communities/organizations/models.py +++ b/backend/communities/organizations/models.py @@ -43,7 +43,6 @@ class Organization(models.Model): acceptance_date = models.DateTimeField(blank=True, null=True) deletion_date = models.DateTimeField(blank=True, null=True) - social_links = models.ManyToManyField("content.SocialLink", blank=True) topics = models.ManyToManyField("content.Topic", blank=True) faqs = models.ManyToManyField("content.Faq", blank=True) resources = models.ManyToManyField("content.Resource", blank=True) @@ -99,6 +98,20 @@ def __str__(self) -> str: return f"{self.id}" +class OrganizationSocialLink(models.Model): + org = models.ForeignKey( + Organization, on_delete=models.CASCADE, null=True, related_name="social_links" + ) + link = models.CharField(max_length=255) + label = models.CharField(max_length=255) + order = models.IntegerField() + creation_date = models.DateTimeField(auto_now_add=True) + last_updated = models.DateTimeField(auto_now=True) + + def __str__(self) -> str: + return self.label + + class OrganizationTask(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) org = models.ForeignKey(Organization, on_delete=models.CASCADE) @@ -120,3 +133,6 @@ class OrganizationText(models.Model): description = models.TextField(max_length=2500) get_involved = models.TextField(max_length=500, blank=True) donate_prompt = models.TextField(max_length=500, blank=True) + + def __str__(self) -> str: + return f"{self.org} - {self.iso}" diff --git a/backend/communities/organizations/serializers.py b/backend/communities/organizations/serializers.py index 35e6aa5a7..8b588da4c 100644 --- a/backend/communities/organizations/serializers.py +++ b/backend/communities/organizations/serializers.py @@ -13,6 +13,7 @@ OrganizationApplication, OrganizationImage, OrganizationMember, + OrganizationSocialLink, OrganizationTask, OrganizationText, ) @@ -22,6 +23,14 @@ # MARK: Main Tables +class OrganizationSocialLinkSerializer( + serializers.ModelSerializer[OrganizationSocialLink] +): + class Meta: + model = OrganizationSocialLink + fields = "__all__" + + class OrganizationTextSerializer(serializers.ModelSerializer[OrganizationText]): class Meta: model = OrganizationText @@ -30,6 +39,7 @@ class Meta: class OrganizationSerializer(serializers.ModelSerializer[Organization]): texts = OrganizationTextSerializer(many=True, read_only=True) + social_links = OrganizationSocialLinkSerializer(many=True, read_only=True) location = LocationSerializer(read_only=True) resources = ResourceSerializer(many=True, read_only=True) groups = GroupSerializer(many=True, read_only=True) diff --git a/backend/core/management/commands/populate_db.py b/backend/core/management/commands/populate_db.py index 14a03edb8..826fb805f 100644 --- a/backend/core/management/commands/populate_db.py +++ b/backend/core/management/commands/populate_db.py @@ -7,15 +7,20 @@ from authentication.factories import UserFactory from authentication.models import UserModel -from communities.groups.factories import GroupFactory, GroupTextFactory +from communities.groups.factories import ( + GroupFactory, + GroupSocialLinkFactory, + GroupTextFactory, +) from communities.groups.models import Group from communities.organizations.factories import ( OrganizationFactory, + OrganizationSocialLinkFactory, OrganizationTextFactory, ) from communities.organizations.models import Organization from content.models import Topic -from events.factories import EventFactory, EventTextFactory +from events.factories import EventFactory, EventSocialLinkFactory, EventTextFactory from events.models import Event @@ -66,14 +71,22 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: user.topics.set([user_topic]) for o in range(num_orgs_per_user): - org_texts = OrganizationTextFactory(iso="en", primary=True) user_org = OrganizationFactory( created_by=user, org_name=f"organization_u{u}_o{o}", name=f"{user_topic.name} Organization", tagline=f"Fighting for {user_topic.name.lower()}", ) + + org_texts = OrganizationTextFactory(iso="en", primary=True) + org_social_links = [] + org_social_links.extend( + OrganizationSocialLinkFactory(label=f"social link {i}", order=i) + for i in range(3) + ) + user_org.texts.set([org_texts]) + user_org.social_links.set(org_social_links) for e in range(num_events_per_org): event_type = random.choice(["learn", "action"]) @@ -92,7 +105,14 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) event_texts = EventTextFactory(iso="en", primary=True) + event_social_links = [] + event_social_links.extend( + EventSocialLinkFactory(label=f"social link {i}", order=i) + for i in range(3) + ) + user_org_event.texts.set([event_texts]) + user_org_event.social_links.set(event_social_links) for g in range(num_groups_per_org): user_org_group = GroupFactory( @@ -103,7 +123,14 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) group_texts = GroupTextFactory(iso="en", primary=True) + group_social_links = [] + group_social_links.extend( + GroupSocialLinkFactory(label=f"social link {i}", order=i) + for i in range(3) + ) + user_org_group.texts.set([group_texts]) + user_org_group.social_links.set(group_social_links) num_orgs = num_users * num_orgs_per_user num_groups = num_users * num_orgs_per_user * num_groups_per_org diff --git a/backend/events/factories.py b/backend/events/factories.py index 72fec8ffe..4edfbe551 100644 --- a/backend/events/factories.py +++ b/backend/events/factories.py @@ -8,6 +8,7 @@ Event, EventAttendee, EventAttendeeStatus, + EventSocialLink, EventText, Format, Role, @@ -100,6 +101,21 @@ class Meta: status_name = factory.Faker("word") +class EventSocialLinkFactory(factory.django.DjangoModelFactory): + class Meta: + model = EventSocialLink + + link = "https://www.activist.org" + label = "social link" + order = random.randint(0, 10) + creation_date = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + last_updated = factory.LazyFunction( + lambda: datetime.datetime.now(tz=datetime.timezone.utc) + ) + + class EventTextFactory(factory.django.DjangoModelFactory): class Meta: model = EventText diff --git a/backend/events/models.py b/backend/events/models.py index b5fa93a8d..907717e2a 100644 --- a/backend/events/models.py +++ b/backend/events/models.py @@ -44,7 +44,6 @@ class Event(models.Model): faqs = models.ManyToManyField("content.Faq", blank=True) formats = models.ManyToManyField("events.Format", blank=True) roles = models.ManyToManyField("events.Role", blank=True) - social_links = models.ManyToManyField("content.SocialLink", blank=True) tags = models.ManyToManyField("content.Tag", blank=True) tasks = models.ManyToManyField("content.Task", blank=True) topics = models.ManyToManyField("content.Topic", blank=True) @@ -105,6 +104,20 @@ def __str__(self) -> str: return self.status_name +class EventSocialLink(models.Model): + event = models.ForeignKey( + Event, on_delete=models.CASCADE, null=True, related_name="social_links" + ) + link = models.CharField(max_length=255) + label = models.CharField(max_length=255) + order = models.IntegerField() + creation_date = models.DateTimeField(auto_now_add=True) + last_updated = models.DateTimeField(auto_now=True) + + def __str__(self) -> str: + return self.label + + class EventText(models.Model): event = models.ForeignKey( Event, on_delete=models.CASCADE, null=True, related_name="texts" @@ -115,4 +128,4 @@ class EventText(models.Model): get_involved = models.TextField(max_length=500, blank=True) def __str__(self) -> str: - return f"{self.id}" + return f"{self.event} - {self.iso}" diff --git a/backend/events/serializers.py b/backend/events/serializers.py index 3769e9291..79abc4ce7 100644 --- a/backend/events/serializers.py +++ b/backend/events/serializers.py @@ -11,7 +11,7 @@ from communities.organizations.models import Organization from content.serializers import LocationSerializer, ResourceSerializer -from events.models import Event, EventText, Format +from events.models import Event, EventSocialLink, EventText, Format from utils.utils import ( validate_creation_and_deletion_dates, validate_creation_and_deprecation_dates, @@ -20,6 +20,12 @@ # MARK: Main Tables +class EventSocialLinkSerializer(serializers.ModelSerializer[EventSocialLink]): + class Meta: + model = EventSocialLink + fields = "__all__" + + class EventTextSerializer(serializers.ModelSerializer[EventText]): class Meta: model = EventText @@ -34,6 +40,7 @@ class Meta: class EventSerializer(serializers.ModelSerializer[Event]): texts = EventTextSerializer(many=True, read_only=True) + social_links = EventSocialLinkSerializer(many=True, read_only=True) offline_location = LocationSerializer(read_only=True) resources = ResourceSerializer(many=True, read_only=True) orgs = EventOrganizationSerializer(read_only=True) diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 412c7a929..1c666de59 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -23,36 +23,37 @@
    -
  • - +
  • import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue"; -import type { Group } from "~/types/communities/group"; -import type { Organization } from "~/types/communities/organization"; -import type { Event } from "~/types/events/event"; +import type { Group, GroupSocialLink } from "~/types/communities/group"; +import type { + Organization, + OrganizationSocialLink, +} from "~/types/communities/organization"; +import type { SocialLink } from "~/types/content/social-link"; +import type { Event, EventSocialLink } from "~/types/events/event"; import { i18nMap } from "~/types/i18n-map"; import { IconMap } from "~/types/icon-map"; import type { AddPayload } from "~/types/social-links-payload"; @@ -161,8 +166,23 @@ if (props.pageType == "organization") { event = eventStore.event; } +const defaultSocialLinks: SocialLink[] = [ + { + link: "", + label: "", + order: 0, + creationDate: "", + lastUpdated: "", + }, +]; + const editModeEnabled = ref(false); -const socialLinksRef = computed(() => { +const socialLinksRef = computed< + | OrganizationSocialLink[] + | GroupSocialLink[] + | EventSocialLink[] + | SocialLink[] +>(() => { if (props.pageType == "organization") { return organization.socialLinks; } else if (props.pageType == "group") { @@ -170,7 +190,7 @@ const socialLinksRef = computed(() => { } else if (props.pageType == "event") { return event.socialLinks; } else { - return [""]; + return defaultSocialLinks; } }); diff --git a/frontend/stores/event.ts b/frontend/stores/event.ts index 4a3bce138..91bcdcbf6 100644 --- a/frontend/stores/event.ts +++ b/frontend/stores/event.ts @@ -36,7 +36,7 @@ export const useEventStore = defineStore("event", { }, getInvolvedUrl: "", - socialLinks: [""], + socialLinks: [], startTime: "", endTime: "", creationDate: "", diff --git a/frontend/stores/group.ts b/frontend/stores/group.ts index 60495bb57..9e9c1de32 100644 --- a/frontend/stores/group.ts +++ b/frontend/stores/group.ts @@ -36,7 +36,7 @@ export const useGroupStore = defineStore("group", { location: { id: "", lat: "", lon: "", bbox: [""], displayName: "" }, getInvolvedUrl: "", - socialLinks: [""], + socialLinks: [], creationDate: "", faqEntries: [""], diff --git a/frontend/stores/organization.ts b/frontend/stores/organization.ts index e6fbf34c2..89c68b667 100644 --- a/frontend/stores/organization.ts +++ b/frontend/stores/organization.ts @@ -30,7 +30,7 @@ export const useOrganizationStore = defineStore("organization", { location: { id: "", lat: "", lon: "", bbox: [""], displayName: "" }, getInvolvedUrl: "", - socialLinks: [""], + socialLinks: [], status: 1, creationDate: "", @@ -239,9 +239,9 @@ export const useOrganizationStore = defineStore("organization", { const token = localStorage.getItem("accessToken"); const responseSocialLinks = await useFetch( - `${BASE_BACKEND_URL as string}/content/social_links/${org.id}/`, + `${BASE_BACKEND_URL as string}/communities/organization_social_links/`, { - method: "PUT", + method: "POST", body: JSON.stringify({ link: payload.link, label: payload.label, diff --git a/frontend/types/communities/group.d.ts b/frontend/types/communities/group.d.ts index 588918cae..353117f07 100644 --- a/frontend/types/communities/group.d.ts +++ b/frontend/types/communities/group.d.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // Note: We need to import here to overwrite base types. import type { Location } from "~/types/content/location"; +import type { SocialLink } from "~/types/content/social-link"; import type { Event } from "~/types/events/event"; // MARK: Main Table @@ -14,7 +15,7 @@ interface GroupBase { iconUrl?: string; location: Location; getInvolvedUrl: string; - socialLinks: string[]; + socialLinks: GroupSocialLink[]; creationDate: string; org: GroupOrganization; events?: Event[]; @@ -45,6 +46,10 @@ export interface GroupMember { isComms: boolean; } +export interface GroupSocialLink extends SocialLink { + groupId: string; +} + export interface GroupText { id: number; groupId: string; diff --git a/frontend/types/communities/organization.d.ts b/frontend/types/communities/organization.d.ts index 701a81600..00e5ca09e 100644 --- a/frontend/types/communities/organization.d.ts +++ b/frontend/types/communities/organization.d.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // Note: We need to import here to overwrite base types. import type { Location } from "~/types/content/location"; +import type { SocialLink } from "~/types/content/social-link"; import type { Event } from "~/types/events/event"; // MARK: Main Table @@ -14,7 +15,7 @@ interface OrganizationBase { iconUrl: string; location: Location; getInvolvedUrl: string; - socialLinks: string[]; + socialLinks: OrganizationSocialLink[]; status: number; // statusUpdated?: string; // acceptanceDate?: string; @@ -53,6 +54,10 @@ export interface OrganizationMember { isComms: boolean; } +export interface OrganizationSocialLink extends SocialLink { + orgId: string; +} + export interface OrganizationText { id: number; orgId: string; diff --git a/frontend/types/content/social-link.d.ts b/frontend/types/content/social-link.d.ts new file mode 100644 index 000000000..7d4d592a1 --- /dev/null +++ b/frontend/types/content/social-link.d.ts @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +export interface SocialLink { + link: string; + label: string; + order: number; + creationDate: string; + lastUpdated: string; +} diff --git a/frontend/types/events/event.d.ts b/frontend/types/events/event.d.ts index dfbe3384b..57f7c95a6 100644 --- a/frontend/types/events/event.d.ts +++ b/frontend/types/events/event.d.ts @@ -1,5 +1,6 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import type { Location } from "~/types/content/location"; +import type { SocialLink } from "~/types/content/social-link"; // MARK: Main Table @@ -13,7 +14,7 @@ interface EventBase { onlineLocationLink?: string; offlineLocation?: Location; getInvolvedUrl?: string; - socialLinks: string[]; + socialLinks: EventSocialLink[]; startTime: string; endTime?: string; creationDate?: string; @@ -39,6 +40,10 @@ export interface EventAttendee { attendeeStatus: int; } +export interface EventSocialLink extends SocialLink { + eventId: string; +} + export interface EventText { id: number; eventId: string; From c4622664a91f7d1438a83bbcc2659355b208b3fa Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Sun, 19 Jan 2025 12:58:07 +0100 Subject: [PATCH 06/14] Fix mypy errors in backend population command --- backend/core/management/commands/populate_db.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/core/management/commands/populate_db.py b/backend/core/management/commands/populate_db.py index 826fb805f..876c997ba 100644 --- a/backend/core/management/commands/populate_db.py +++ b/backend/core/management/commands/populate_db.py @@ -1,7 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import random from argparse import ArgumentParser -from typing import TypedDict, Unpack +from typing import List, TypedDict, Unpack from django.core.management.base import BaseCommand @@ -79,7 +79,7 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) org_texts = OrganizationTextFactory(iso="en", primary=True) - org_social_links = [] + org_social_links: List[OrganizationSocialLinkFactory] = [] org_social_links.extend( OrganizationSocialLinkFactory(label=f"social link {i}", order=i) for i in range(3) @@ -105,7 +105,7 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) event_texts = EventTextFactory(iso="en", primary=True) - event_social_links = [] + event_social_links: List[EventSocialLinkFactory] = [] event_social_links.extend( EventSocialLinkFactory(label=f"social link {i}", order=i) for i in range(3) @@ -123,7 +123,7 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: ) group_texts = GroupTextFactory(iso="en", primary=True) - group_social_links = [] + group_social_links: List[GroupSocialLinkFactory] = [] group_social_links.extend( GroupSocialLinkFactory(label=f"social link {i}", order=i) for i in range(3) From 2b864174be4b607940bacaab74242128f12d038e Mon Sep 17 00:00:00 2001 From: Matt Burnett Date: Sun, 19 Jan 2025 16:19:35 +0100 Subject: [PATCH 07/14] 1006.2 Partial commit. Chages to backend views, urls, etc. --- backend/communities/organizations/views.py | 25 ++++++++++++++++++ backend/communities/urls.py | 15 ++++++++++- backend/content/models.py | 2 +- backend/content/serializers.py | 7 +++++ backend/content/urls.py | 1 + backend/content/views.py | 27 ++++++++++++++++++- frontend/components/card/CardConnect.vue | 30 +++++++++++++++++----- frontend/stores/organization.ts | 14 +++++++--- frontend/types/social-links-payload.ts | 1 + 9 files changed, 110 insertions(+), 12 deletions(-) diff --git a/backend/communities/organizations/views.py b/backend/communities/organizations/views.py index 531c77b3e..a113f475f 100644 --- a/backend/communities/organizations/views.py +++ b/backend/communities/organizations/views.py @@ -1,5 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # mypy: disable-error-code="override" +from asyncio.log import logger + from django.utils import timezone from rest_framework import status, viewsets from rest_framework.request import Request @@ -15,11 +17,34 @@ OrganizationSerializer, OrganizationTextSerializer, ) +from content.models import SocialLink +from content.serializers import SocialLinkSerializer from core.paginator import CustomPagination # MARK: Main Tables +class OrganizationSocialLinkViewSet(viewsets.ModelViewSet): + # from communities.organizations.models import Organization + # + queryset = SocialLink.objects.all() + serializer_class = SocialLinkSerializer + + def create(self, request, *args, **kwargs): + logger.warning("POST request received") + logger.warning(f"Request.body: {request.body.decode('utf8')}") + + # return super().create(request, *args, **kwargs) + return Response(status=status.HTTP_201_CREATED) + + # def create_social_link(self, request, pk=None): + # org_id = request.data.get('organization_id') + # org = self.Organization.objects.get(id=org_id) + # social_link = self.get_object() + # org.social_links.add(social_link) + # return Response(status=status.HTTP_201_CREATED) + + class OrganizationViewSet(viewsets.ModelViewSet[Organization]): queryset = Organization.objects.all() serializer_class = OrganizationSerializer diff --git a/backend/communities/urls.py b/backend/communities/urls.py index c9bb8111f..fd65b090e 100644 --- a/backend/communities/urls.py +++ b/backend/communities/urls.py @@ -3,9 +3,15 @@ from rest_framework.routers import DefaultRouter from communities.groups.views import GroupTextViewSet, GroupViewSet -from communities.organizations.views import OrganizationTextViewSet, OrganizationViewSet +from communities.organizations.views import ( + OrganizationSocialLinkViewSet, + OrganizationTextViewSet, + OrganizationViewSet, +) from communities.views import StatusViewSet +# from content import views + app_name = "communities" router = DefaultRouter() @@ -28,6 +34,13 @@ ) router.register(prefix=r"statuses", viewset=StatusViewSet) +# router.register(prefix=r"social_links", viewset=views.SocialLinkViewSet) +router.register( + prefix=r"organization_social_links", + viewset=OrganizationSocialLinkViewSet, + basename="organization-social-links", +) + urlpatterns = [ path("", include(router.urls)), ] diff --git a/backend/content/models.py b/backend/content/models.py index 749f08dcb..f42194e4a 100644 --- a/backend/content/models.py +++ b/backend/content/models.py @@ -102,7 +102,7 @@ class SocialLink(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) link = models.CharField(max_length=255) label = models.CharField(max_length=255) - order = models.IntegerField() + order = models.IntegerField(default=0) creation_date = models.DateTimeField(auto_now_add=True) last_updated = models.DateTimeField(auto_now=True) diff --git a/backend/content/serializers.py b/backend/content/serializers.py index c8457e557..53389af32 100644 --- a/backend/content/serializers.py +++ b/backend/content/serializers.py @@ -16,6 +16,7 @@ Image, Location, Resource, + SocialLink, Topic, ) from utils.utils import validate_creation_and_deprecation_dates @@ -83,6 +84,12 @@ class Meta: fields = "__all__" +class SocialLinkSerializer(serializers.ModelSerializer): + class Meta: + model = SocialLink + fields = ["id", "link", "label", "order"] + + class TopicSerializer(serializers.ModelSerializer[Topic]): class Meta: model = Topic diff --git a/backend/content/urls.py b/backend/content/urls.py index fe618b6c7..68c47aa32 100644 --- a/backend/content/urls.py +++ b/backend/content/urls.py @@ -12,6 +12,7 @@ router.register(prefix=r"discussions", viewset=views.DiscussionViewSet) router.register(prefix=r"resources", viewset=views.ResourceViewSet) +# router.register(prefix=r"social_links", viewset=views.SocialLinkViewSet) # MARK: Bridge Tables diff --git a/backend/content/views.py b/backend/content/views.py index 18c7b96b3..3949e9f8e 100644 --- a/backend/content/views.py +++ b/backend/content/views.py @@ -1,16 +1,20 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # mypy: disable-error-code="override" + +from asyncio.log import logger + from django.db.models import Q from rest_framework import status, viewsets from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.request import Request from rest_framework.response import Response -from content.models import Discussion, DiscussionEntry, Resource +from content.models import Discussion, DiscussionEntry, Resource, SocialLink from content.serializers import ( DiscussionEntrySerializer, DiscussionSerializer, ResourceSerializer, + SocialLinkSerializer, ) from core.paginator import CustomPagination @@ -207,6 +211,27 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) +class SocialLinkViewSet(viewsets.ModelViewSet): + # from communities.organizations.models import Organization + # + queryset = SocialLink.objects.all() + serializer_class = SocialLinkSerializer + + def create(self, request, *args, **kwargs): + logger.warning("POST request received") + logger.warning(f"Request.body: {request.body.decode('utf8')}") + + # return super().create(request, *args, **kwargs) + return Response(status=status.HTTP_201_CREATED) + + # def create_social_link(self, request, pk=None): + # org_id = request.data.get('organization_id') + # org = self.Organization.objects.get(id=org_id) + # social_link = self.get_object() + # org.social_links.add(social_link) + # return Response(status=status.HTTP_201_CREATED) + + # MARK: Bridge Tables diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 1c666de59..3e0f5bd44 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -196,19 +196,37 @@ const socialLinksRef = computed< const handlePopupAddClick = async (payload: AddPayload, close: () => void) => { // Put the social links payload in the database. - // TODO: Needs more robust handling (ie try/catch + error trapping/handling) - const response = await organizationStore.addSocialLinks( + const response = await organizationStore.createSocialMediaLink( organization, payload ); - if (response) { - console.log("org store addSocialLinks response: " + response); + + // If the response.status is true, then the social links were added successfully. + // TODO: Figure out how to get better handle error conditions. + if (response.status == true) { + console.log( + "CardConnect createSocialMediaLink response.status: ", + response.status + ); console.log( "CardConnect addSocialLinks payload: " + JSON.stringify(payload) ); - } - close(); + // Close PopupNewField. + close(); + } else { + alert( + `${JSON.stringify(response.data).replace( + /^"|"$/g, + "" + )}. \n\nUnable to add social media link info ${JSON.stringify( + payload.label + )}, + ${JSON.stringify( + payload.link + )}. \n\nYou might be able to find more information in the browser's dev tools console.` + ); + } }; const toggleEditMode = () => { diff --git a/frontend/stores/organization.ts b/frontend/stores/organization.ts index 89c68b667..e1ace9c23 100644 --- a/frontend/stores/organization.ts +++ b/frontend/stores/organization.ts @@ -228,7 +228,7 @@ export const useOrganizationStore = defineStore("organization", { // MARK: Add Social Links - async addSocialLinks(org: Organization, payload: AddPayload) { + async createSocialMediaLink(org: Organization, payload: AddPayload) { // TODO: PUT/POST payload, PUT/POST org and social link id's in bridge table // content/social_links // TODO: Other PUT's/POST's? @@ -240,6 +240,8 @@ export const useOrganizationStore = defineStore("organization", { const responseSocialLinks = await useFetch( `${BASE_BACKEND_URL as string}/communities/organization_social_links/`, + // `${BASE_BACKEND_URL as string}/communities/organizations/social_links/`, + // `${BASE_BACKEND_URL as string}/communities/social_links/`, { method: "POST", body: JSON.stringify({ @@ -253,6 +255,11 @@ export const useOrganizationStore = defineStore("organization", { } ); + console.log( + "createSocialMediaLink responseSocialLinks.data:", + responseSocialLinks.data + ); + const responseSocialLinksData = responseSocialLinks.data .value as unknown as Organization; @@ -260,10 +267,11 @@ export const useOrganizationStore = defineStore("organization", { this.loading = false; // return responseSocialLinksData.id; - return responseSocialLinksData; + // return responseSocialLinksData; + return { status: true, data: "Added social media link data." }; } - return false; + return { status: false, data: "Unable to add social media link data." }; }, // MARK: Delete diff --git a/frontend/types/social-links-payload.ts b/frontend/types/social-links-payload.ts index 452c88470..493aa1584 100644 --- a/frontend/types/social-links-payload.ts +++ b/frontend/types/social-links-payload.ts @@ -1,4 +1,5 @@ export interface AddPayload { link: string; label: string; + order?: number; } From d5377160add9cb7c8836a1d9092c66704f9c0dad Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Tue, 21 Jan 2025 19:56:18 +0100 Subject: [PATCH 08/14] WIP - All necessary changes for the modal based social link UI --- .pre-commit-config.yaml | 14 +- backend/communities/groups/models.py | 11 +- backend/communities/groups/views.py | 13 +- backend/communities/organizations/models.py | 11 +- backend/communities/organizations/views.py | 32 +--- backend/communities/urls.py | 32 ++-- backend/content/serializers.py | 7 - backend/content/urls.py | 1 - backend/content/views.py | 27 +-- .../core/management/commands/populate_db.py | 1 + backend/events/models.py | 11 +- backend/events/views.py | 13 +- frontend/components/card/CardConnect.vue | 142 ++------------ .../components/card/about/CardAboutEvent.vue | 7 +- .../components/card/about/CardAboutGroup.vue | 7 +- .../get-involved/CardGetInvolvedEvent.vue | 44 ++--- .../get-involved/CardGetInvolvedGroup.vue | 44 ++--- .../CardGetInvolvedOrganization.vue | 6 - frontend/components/feed/Feed.vue | 2 +- frontend/components/icon/IconClose.vue | 10 + .../modal/edit/ModalEditSocialLinks.vue | 174 ++++++++++++++++++ .../modal/edit/text/ModalEditTextEvent.vue | 47 ++--- .../modal/edit/text/ModalEditTextGroup.vue | 47 ++--- frontend/components/popup/PopupNewField.vue | 111 ----------- .../sidebar/left/SidebarLeftSelector.vue | 5 +- frontend/i18n/check/i18n_check_unused_keys.py | 2 + frontend/i18n/check/run_i18n_checks.py | 16 +- frontend/i18n/de.json | 6 - frontend/i18n/en-US.json | 13 +- frontend/i18n/es.json | 6 - frontend/i18n/fr.json | 6 - frontend/i18n/pt.json | 6 - frontend/pages/events/[id]/about.vue | 2 + frontend/pages/organizations/[id]/about.vue | 2 + .../[id]/groups/[group-id]/about.vue | 3 +- frontend/stores/event.ts | 8 + frontend/stores/group.ts | 10 +- frontend/stores/organization.ts | 24 +-- frontend/types/content/social-link.d.ts | 6 + frontend/types/i18n-map.ts | 23 ++- frontend/types/social-links-payload.ts | 5 - 41 files changed, 406 insertions(+), 551 deletions(-) create mode 100644 frontend/components/icon/IconClose.vue create mode 100644 frontend/components/modal/edit/ModalEditSocialLinks.vue delete mode 100644 frontend/components/popup/PopupNewField.vue delete mode 100644 frontend/types/social-links-payload.ts diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b76eb26b..531597b11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,13 +16,13 @@ repos: args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - - repo: local - hooks: - - id: run-i18n-checks - name: Run i18n Checks - entry: python frontend/i18n/check/run_i18n_checks.py - language: python - stages: [pre-commit] + # - repo: local + # hooks: + # - id: run-i18n-checks + # name: Run i18n Checks + # entry: python frontend/i18n/check/run_i18n_checks.py + # language: python + # stages: [pre-commit] # - repo: https://github.com/tcort/markdown-link-check # rev: v3.13.6 diff --git a/backend/communities/groups/models.py b/backend/communities/groups/models.py index 955346e9a..09b98194d 100644 --- a/backend/communities/groups/models.py +++ b/backend/communities/groups/models.py @@ -7,6 +7,7 @@ from django.db import models +from content.models import SocialLink from utils.models import ISO_CHOICES # MARK: Main Tables @@ -70,18 +71,10 @@ def __str__(self) -> str: return f"{self.id}" -class GroupSocialLink(models.Model): +class GroupSocialLink(SocialLink): group = models.ForeignKey( Group, on_delete=models.CASCADE, null=True, related_name="social_links" ) - link = models.CharField(max_length=255) - label = models.CharField(max_length=255) - order = models.IntegerField() - creation_date = models.DateTimeField(auto_now_add=True) - last_updated = models.DateTimeField(auto_now=True) - - def __str__(self) -> str: - return self.label class GroupText(models.Model): diff --git a/backend/communities/groups/views.py b/backend/communities/groups/views.py index e1ed80dab..bbc92dbaf 100644 --- a/backend/communities/groups/views.py +++ b/backend/communities/groups/views.py @@ -4,8 +4,12 @@ from rest_framework.request import Request from rest_framework.response import Response -from communities.groups.models import Group, GroupText -from communities.groups.serializers import GroupSerializer, GroupTextSerializer +from communities.groups.models import Group, GroupSocialLink, GroupText +from communities.groups.serializers import ( + GroupSerializer, + GroupSocialLinkSerializer, + GroupTextSerializer, +) from core.paginator import CustomPagination # MARK: Main Tables @@ -119,6 +123,11 @@ def destroy(self, request: Request, *args: str, **kwargs: int) -> Response: ) +class GroupSocialLinkViewSet(viewsets.ModelViewSet[GroupSocialLink]): + queryset = GroupSocialLink.objects.all() + serializer_class = GroupSocialLinkSerializer + + class GroupTextViewSet(viewsets.ModelViewSet[GroupText]): queryset = GroupText.objects.all() serializer_class = GroupTextSerializer diff --git a/backend/communities/organizations/models.py b/backend/communities/organizations/models.py index e3f0948a4..d06777b16 100644 --- a/backend/communities/organizations/models.py +++ b/backend/communities/organizations/models.py @@ -8,6 +8,7 @@ from django.db import models from authentication import enums +from content.models import SocialLink from utils.models import ISO_CHOICES # MARK: Main Tables @@ -98,18 +99,10 @@ def __str__(self) -> str: return f"{self.id}" -class OrganizationSocialLink(models.Model): +class OrganizationSocialLink(SocialLink): org = models.ForeignKey( Organization, on_delete=models.CASCADE, null=True, related_name="social_links" ) - link = models.CharField(max_length=255) - label = models.CharField(max_length=255) - order = models.IntegerField() - creation_date = models.DateTimeField(auto_now_add=True) - last_updated = models.DateTimeField(auto_now=True) - - def __str__(self) -> str: - return self.label class OrganizationTask(models.Model): diff --git a/backend/communities/organizations/views.py b/backend/communities/organizations/views.py index a113f475f..bffd9b6b3 100644 --- a/backend/communities/organizations/views.py +++ b/backend/communities/organizations/views.py @@ -1,7 +1,5 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # mypy: disable-error-code="override" -from asyncio.log import logger - from django.utils import timezone from rest_framework import status, viewsets from rest_framework.request import Request @@ -11,40 +9,19 @@ from communities.organizations.models import ( Organization, OrganizationApplication, + OrganizationSocialLink, OrganizationText, ) from communities.organizations.serializers import ( OrganizationSerializer, + OrganizationSocialLinkSerializer, OrganizationTextSerializer, ) -from content.models import SocialLink -from content.serializers import SocialLinkSerializer from core.paginator import CustomPagination # MARK: Main Tables -class OrganizationSocialLinkViewSet(viewsets.ModelViewSet): - # from communities.organizations.models import Organization - # - queryset = SocialLink.objects.all() - serializer_class = SocialLinkSerializer - - def create(self, request, *args, **kwargs): - logger.warning("POST request received") - logger.warning(f"Request.body: {request.body.decode('utf8')}") - - # return super().create(request, *args, **kwargs) - return Response(status=status.HTTP_201_CREATED) - - # def create_social_link(self, request, pk=None): - # org_id = request.data.get('organization_id') - # org = self.Organization.objects.get(id=org_id) - # social_link = self.get_object() - # org.social_links.add(social_link) - # return Response(status=status.HTTP_201_CREATED) - - class OrganizationViewSet(viewsets.ModelViewSet[Organization]): queryset = Organization.objects.all() serializer_class = OrganizationSerializer @@ -162,6 +139,11 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: ) +class OrganizationSocialLinkViewSet(viewsets.ModelViewSet[OrganizationSocialLink]): + queryset = OrganizationSocialLink.objects.all() + serializer_class = OrganizationSocialLinkSerializer + + class OrganizationTextViewSet(viewsets.ModelViewSet[OrganizationText]): queryset = OrganizationText.objects.all() serializer_class = OrganizationTextSerializer diff --git a/backend/communities/urls.py b/backend/communities/urls.py index fd65b090e..c82dc7356 100644 --- a/backend/communities/urls.py +++ b/backend/communities/urls.py @@ -2,7 +2,11 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from communities.groups.views import GroupTextViewSet, GroupViewSet +from communities.groups.views import ( + GroupSocialLinkViewSet, + GroupTextViewSet, + GroupViewSet, +) from communities.organizations.views import ( OrganizationSocialLinkViewSet, OrganizationTextViewSet, @@ -10,8 +14,6 @@ ) from communities.views import StatusViewSet -# from content import views - app_name = "communities" router = DefaultRouter() @@ -19,27 +21,33 @@ # MARK: Main Tables router.register(prefix=r"groups", viewset=GroupViewSet, basename="group") +router.register( + prefix=r"organizations", viewset=OrganizationViewSet, basename="organization" +) +router.register(prefix=r"statuses", viewset=StatusViewSet) + +# MARK: Bridge Tables + +router.register( + prefix=r"group_social_links", + viewset=GroupSocialLinkViewSet, + basename="group-social-links", +) router.register( prefix=r"group_texts", viewset=GroupTextViewSet, basename="group-text", ) router.register( - prefix=r"organizations", viewset=OrganizationViewSet, basename="organization" + prefix=r"organization_social_links", + viewset=OrganizationSocialLinkViewSet, + basename="organization-social-links", ) router.register( prefix=r"organization_texts", viewset=OrganizationTextViewSet, basename="organization-text", ) -router.register(prefix=r"statuses", viewset=StatusViewSet) - -# router.register(prefix=r"social_links", viewset=views.SocialLinkViewSet) -router.register( - prefix=r"organization_social_links", - viewset=OrganizationSocialLinkViewSet, - basename="organization-social-links", -) urlpatterns = [ path("", include(router.urls)), diff --git a/backend/content/serializers.py b/backend/content/serializers.py index 53389af32..c8457e557 100644 --- a/backend/content/serializers.py +++ b/backend/content/serializers.py @@ -16,7 +16,6 @@ Image, Location, Resource, - SocialLink, Topic, ) from utils.utils import validate_creation_and_deprecation_dates @@ -84,12 +83,6 @@ class Meta: fields = "__all__" -class SocialLinkSerializer(serializers.ModelSerializer): - class Meta: - model = SocialLink - fields = ["id", "link", "label", "order"] - - class TopicSerializer(serializers.ModelSerializer[Topic]): class Meta: model = Topic diff --git a/backend/content/urls.py b/backend/content/urls.py index 68c47aa32..fe618b6c7 100644 --- a/backend/content/urls.py +++ b/backend/content/urls.py @@ -12,7 +12,6 @@ router.register(prefix=r"discussions", viewset=views.DiscussionViewSet) router.register(prefix=r"resources", viewset=views.ResourceViewSet) -# router.register(prefix=r"social_links", viewset=views.SocialLinkViewSet) # MARK: Bridge Tables diff --git a/backend/content/views.py b/backend/content/views.py index 3949e9f8e..18c7b96b3 100644 --- a/backend/content/views.py +++ b/backend/content/views.py @@ -1,20 +1,16 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # mypy: disable-error-code="override" - -from asyncio.log import logger - from django.db.models import Q from rest_framework import status, viewsets from rest_framework.permissions import IsAuthenticatedOrReadOnly from rest_framework.request import Request from rest_framework.response import Response -from content.models import Discussion, DiscussionEntry, Resource, SocialLink +from content.models import Discussion, DiscussionEntry, Resource from content.serializers import ( DiscussionEntrySerializer, DiscussionSerializer, ResourceSerializer, - SocialLinkSerializer, ) from core.paginator import CustomPagination @@ -211,27 +207,6 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) -class SocialLinkViewSet(viewsets.ModelViewSet): - # from communities.organizations.models import Organization - # - queryset = SocialLink.objects.all() - serializer_class = SocialLinkSerializer - - def create(self, request, *args, **kwargs): - logger.warning("POST request received") - logger.warning(f"Request.body: {request.body.decode('utf8')}") - - # return super().create(request, *args, **kwargs) - return Response(status=status.HTTP_201_CREATED) - - # def create_social_link(self, request, pk=None): - # org_id = request.data.get('organization_id') - # org = self.Organization.objects.get(id=org_id) - # social_link = self.get_object() - # org.social_links.add(social_link) - # return Response(status=status.HTTP_201_CREATED) - - # MARK: Bridge Tables diff --git a/backend/core/management/commands/populate_db.py b/backend/core/management/commands/populate_db.py index 876c997ba..740012be3 100644 --- a/backend/core/management/commands/populate_db.py +++ b/backend/core/management/commands/populate_db.py @@ -120,6 +120,7 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None: group_name=f"group_u{u}_o{o}_g{g}", name=f"{user_topic.name} Group", org=user_org, + tagline=f"Fighting for {user_topic.name.lower()}", ) group_texts = GroupTextFactory(iso="en", primary=True) diff --git a/backend/events/models.py b/backend/events/models.py index 907717e2a..15fa2d7fa 100644 --- a/backend/events/models.py +++ b/backend/events/models.py @@ -7,6 +7,7 @@ from django.db import models +from content.models import SocialLink from utils.models import ISO_CHOICES # MARK: Main Tables @@ -104,18 +105,10 @@ def __str__(self) -> str: return self.status_name -class EventSocialLink(models.Model): +class EventSocialLink(SocialLink): event = models.ForeignKey( Event, on_delete=models.CASCADE, null=True, related_name="social_links" ) - link = models.CharField(max_length=255) - label = models.CharField(max_length=255) - order = models.IntegerField() - creation_date = models.DateTimeField(auto_now_add=True) - last_updated = models.DateTimeField(auto_now=True) - - def __str__(self) -> str: - return self.label class EventText(models.Model): diff --git a/backend/events/views.py b/backend/events/views.py index fbe59ac55..0772d89a6 100644 --- a/backend/events/views.py +++ b/backend/events/views.py @@ -4,8 +4,12 @@ from rest_framework.response import Response from core.paginator import CustomPagination -from events.models import Event, EventText -from events.serializers import EventSerializer, EventTextSerializer +from events.models import Event, EventSocialLink, EventText +from events.serializers import ( + EventSerializer, + EventSocialLinkSerializer, + EventTextSerializer, +) # MARK: Main Tables @@ -115,6 +119,11 @@ def destroy(self, request: Request, *args: str, **kwargs: int) -> Response: ) +class EventSocialLinkViewSet(viewsets.ModelViewSet[EventSocialLink]): + queryset = EventSocialLink.objects.all() + serializer_class = EventSocialLinkSerializer + + class EventTextViewSet(viewsets.ModelViewSet[EventText]): queryset = EventText.objects.all() serializer_class = EventTextSerializer diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 3e0f5bd44..b1d44715e 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -5,20 +5,11 @@

    {{ $t(i18nMap.components._global.connect) }}

    -
    - - -
    +
      - @@ -55,74 +40,12 @@
-
- - - - - - - - - - - - -
diff --git a/frontend/components/card/about/CardAboutEvent.vue b/frontend/components/card/about/CardAboutEvent.vue index 03b8f02a8..398404e0a 100644 --- a/frontend/components/card/about/CardAboutEvent.vue +++ b/frontend/components/card/about/CardAboutEvent.vue @@ -7,8 +7,9 @@ {{ $t(i18nMap._global.about) }}
@@ -65,6 +66,8 @@ import { i18nMap } from "~/types/i18n-map"; const { openModal: openModalEditTextEvent } = useModalHandlers("ModalEditTextEvent"); +const { userIsSignedIn } = useUser(); + const idParam = useRoute().params.id; const id = typeof idParam === "string" ? idParam : undefined; diff --git a/frontend/components/card/about/CardAboutGroup.vue b/frontend/components/card/about/CardAboutGroup.vue index bbc75c57e..d447f303c 100644 --- a/frontend/components/card/about/CardAboutGroup.vue +++ b/frontend/components/card/about/CardAboutGroup.vue @@ -18,8 +18,9 @@ {{ $t(i18nMap._global.about) }}
@@ -86,6 +87,8 @@ import { IconMap } from "~/types/icon-map"; const { openModal: openModalEditTextGroup } = useModalHandlers("ModalEditTextGroup"); +const { userIsSignedIn } = useUser(); + const idParam = useRoute().params.id; const id = typeof idParam === "string" ? idParam : undefined; diff --git a/frontend/components/card/get-involved/CardGetInvolvedEvent.vue b/frontend/components/card/get-involved/CardGetInvolvedEvent.vue index 4a5761bb3..a8f127311 100644 --- a/frontend/components/card/get-involved/CardGetInvolvedEvent.vue +++ b/frontend/components/card/get-involved/CardGetInvolvedEvent.vue @@ -5,17 +5,10 @@

{{ $t(i18nMap.components._global.participate) }}

- -
@@ -44,27 +37,20 @@ diff --git a/frontend/components/card/get-involved/CardGetInvolvedGroup.vue b/frontend/components/card/get-involved/CardGetInvolvedGroup.vue index 0a91b2898..c98a1e604 100644 --- a/frontend/components/card/get-involved/CardGetInvolvedGroup.vue +++ b/frontend/components/card/get-involved/CardGetInvolvedGroup.vue @@ -5,18 +5,10 @@

{{ $t(i18nMap.components._global.get_involved) }}

- -
{{ $t(i18nMap.components._global.join_group_subtext, { - org_name: group.name, + entity_name: group.name, }) }}.

@@ -44,26 +36,20 @@ diff --git a/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue b/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue index e7046f631..87438b3c1 100644 --- a/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue +++ b/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue @@ -32,12 +32,6 @@ ariaLabel="_global.join_organization_aria_label" />
-
diff --git a/frontend/components/feed/Feed.vue b/frontend/components/feed/Feed.vue index e7ba40a18..f1ff1a245 100644 --- a/frontend/components/feed/Feed.vue +++ b/frontend/components/feed/Feed.vue @@ -4,7 +4,7 @@ v-if="feedItemUrls && feedItemNames" class="mt-3 flex items-center justify-start space-x-3" > -
+
diff --git a/frontend/components/icon/IconClose.vue b/frontend/components/icon/IconClose.vue new file mode 100644 index 000000000..9beda2a42 --- /dev/null +++ b/frontend/components/icon/IconClose.vue @@ -0,0 +1,10 @@ + + + + diff --git a/frontend/components/modal/edit/ModalEditSocialLinks.vue b/frontend/components/modal/edit/ModalEditSocialLinks.vue new file mode 100644 index 000000000..195e1d5a0 --- /dev/null +++ b/frontend/components/modal/edit/ModalEditSocialLinks.vue @@ -0,0 +1,174 @@ + + + + diff --git a/frontend/components/modal/edit/text/ModalEditTextEvent.vue b/frontend/components/modal/edit/text/ModalEditTextEvent.vue index 11aeb568b..5c458a668 100644 --- a/frontend/components/modal/edit/text/ModalEditTextEvent.vue +++ b/frontend/components/modal/edit/text/ModalEditTextEvent.vue @@ -1,10 +1,6 @@ diff --git a/frontend/components/modal/edit/text/ModalEditTextGroup.vue b/frontend/components/modal/edit/text/ModalEditTextGroup.vue index 9f669fee8..f2f921c9c 100644 --- a/frontend/components/modal/edit/text/ModalEditTextGroup.vue +++ b/frontend/components/modal/edit/text/ModalEditTextGroup.vue @@ -1,10 +1,6 @@ diff --git a/frontend/components/popup/PopupNewField.vue b/frontend/components/popup/PopupNewField.vue deleted file mode 100644 index 14a930724..000000000 --- a/frontend/components/popup/PopupNewField.vue +++ /dev/null @@ -1,111 +0,0 @@ - - - - diff --git a/frontend/components/sidebar/left/SidebarLeftSelector.vue b/frontend/components/sidebar/left/SidebarLeftSelector.vue index 98d5c98e6..2cb5b0f05 100644 --- a/frontend/components/sidebar/left/SidebarLeftSelector.vue +++ b/frontend/components/sidebar/left/SidebarLeftSelector.vue @@ -2,7 +2,10 @@