diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7c977ce57..f239e9c76 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -200,7 +200,7 @@ git remote add upstream https://github.com/activist-org/activist.git
# docker compose --env-file .env.dev down
```
-5. You can visit to see the development build once the container is up and running.
+5. You can visit to see the development build once the container is up and running. From there click `View organizations` or `View events` to explore the platform.
> [!NOTE]
> Feel free to contact the team in the [Development room on Matrix](https://matrix.to/#/!CRgLpGeOBNwxYCtqmK:matrix.org?via=matrix.org&via=acter.global&via=chat.0x7cd.xyz) if you're having problems getting your environment setup!
diff --git a/README.md b/README.md
index d28c5c081..a6ec8fcd5 100644
--- a/README.md
+++ b/README.md
@@ -210,7 +210,7 @@ git remote add upstream https://github.com/activist-org/activist.git
# docker compose --env-file .env.dev down
```
-6. You can then visit to see the development frontend build once the container is up and running.
+6. You can then visit to see the development frontend build once the container is up and running. From there click `View organizations` or `View events` to explore the platform.
> [!NOTE]
> Feel free to contact the team in the [Development room on Matrix](https://matrix.to/#/!CRgLpGeOBNwxYCtqmK:matrix.org?via=matrix.org&via=acter.global&via=chat.0x7cd.xyz) if you're having problems getting your environment setup! If you're having issues with Docker and just want to get the frontend or backend up and running, please see [the section on this in the contributing guide](https://github.com/activist-org/activist/blob/main/CONTRIBUTING.md#using-yarn-or-python).
diff --git a/backend/authentication/admin.py b/backend/authentication/admin.py
index 9761b2108..ebd476ccb 100644
--- a/backend/authentication/admin.py
+++ b/backend/authentication/admin.py
@@ -86,7 +86,7 @@ class UserAdmin(BaseUserAdmin):
add_form = UserCreationForm
# The fields to be used in displaying the User model.
- list_display = ["email", "is_admin"]
+ list_display = ["username", "email", "is_admin"]
list_filter = ["is_admin"]
fieldsets = [
(None, {"fields": ["email", "password"]}),
diff --git a/backend/authentication/factories.py b/backend/authentication/factories.py
index e833d3782..987419dc1 100644
--- a/backend/authentication/factories.py
+++ b/backend/authentication/factories.py
@@ -39,6 +39,7 @@ class Meta:
username = factory.Faker("user_name")
name = factory.Faker("name")
+ location = factory.Faker("city")
description = factory.Faker("text", max_nb_chars=500)
verified = factory.Faker("boolean")
verification_method = factory.Faker("word")
diff --git a/backend/authentication/models.py b/backend/authentication/models.py
index 5f1f3d18b..4ad430c5e 100644
--- a/backend/authentication/models.py
+++ b/backend/authentication/models.py
@@ -87,6 +87,7 @@ class UserModel(AbstractUser, PermissionsMixin):
username = models.CharField(max_length=255, unique=True)
name = models.CharField(max_length=255, blank=True)
password = models.CharField(max_length=255)
+ location = models.CharField(max_length=100, blank=True)
description = models.TextField(max_length=500, blank=True)
verified = models.BooleanField(default=False)
verification_method = models.CharField(max_length=30, blank=True)
diff --git a/backend/backend/management/commands/populate_db.py b/backend/backend/management/commands/populate_db.py
index 262bd4280..e799d7a94 100644
--- a/backend/backend/management/commands/populate_db.py
+++ b/backend/backend/management/commands/populate_db.py
@@ -1,10 +1,12 @@
+import random
from argparse import ArgumentParser
from typing import TypedDict, Unpack
from django.core.management.base import BaseCommand
-from authentication.factories import UserFactory
+from authentication.factories import UserFactory, UserTopicFactory
from authentication.models import UserModel
+from content.models import Topic
from entities.factories import (
GroupFactory,
GroupTextFactory,
@@ -18,9 +20,9 @@
class Options(TypedDict):
users: int
- orgs: int
- groups: int
- events: int
+ orgs_per_user: int
+ groups_per_org: int
+ events_per_org: int
class Command(BaseCommand):
@@ -28,15 +30,15 @@ class Command(BaseCommand):
def add_arguments(self, parser: ArgumentParser) -> None:
parser.add_argument("--users", type=int, default=10)
- parser.add_argument("--opu", type=int, default=1) # orgs per user
- parser.add_argument("--gpo", type=int, default=1) # groups per org
- parser.add_argument("--epo", type=int, default=1) # events per org
+ 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)
def handle(self, *args: str, **options: Unpack[Options]) -> None:
- n_users = options.get("users")
- n_orgs_per_user = options.get("opu")
- n_groups_per_org = options.get("gpo")
- n_events_per_org = options.get("epo")
+ 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"]
# Clear all tables before creating new data.
UserModel.objects.exclude(username="admin").delete()
@@ -44,73 +46,60 @@ def handle(self, *args: str, **options: Unpack[Options]) -> None:
Group.objects.all().delete()
Event.objects.all().delete()
+ topics = Topic.objects.all()
+
try:
users = [
UserFactory(username=f"activist_{i}", name=f"Activist {i}")
- for i in range(n_users)
+ for i in range(num_users)
]
- for i, user in enumerate(users):
- user_location = "Berlin"
- user_topic = "Climate"
+ for u, user in enumerate(users):
+ user_topic = random.choice(topics)
+ UserTopicFactory(user_id=user, topic_id=user_topic)
- for _ in range(n_orgs_per_user):
+ for o in range(num_orgs_per_user):
user_org = OrganizationFactory(
- name=f"{user_location} {user_topic} Organization {i}",
+ name=f"{user_topic.name} Organization (U{u}:O{o})",
created_by=user,
)
- OrganizationTextFactory(
- org_id=user_org,
- iso="en",
- primary=True,
- description="This is an org",
- get_involved="Get involved!",
- donate_prompt="Donate!",
- )
+ OrganizationTextFactory(org_id=user_org, iso="wt", primary=True)
- for g in range(n_groups_per_org):
+ for g in range(num_groups_per_org):
user_org_group = GroupFactory(
org_id=user_org,
- name=f"{user_location} {user_topic} Group {i}-{g}",
+ name=f"{user_topic.name} Group (U{u}:O{o}:G{g})",
created_by=user,
)
GroupTextFactory(
- group_id=user_org_group,
- iso="en",
- primary=True,
- description="This is a group",
- get_involved="Get involved!",
- donate_prompt="Donate!",
+ group_id=user_org_group, iso="en", primary=True
)
- for e in range(n_events_per_org):
+ for e in range(num_events_per_org):
user_org_event = EventFactory(
- name=f"{user_location} {user_topic} Event {i}-{e}",
+ name=f"{user_topic.name} Event (U{u}:O{o}:E{e})",
+ type=random.choice(["learn", "action"]),
created_by=user,
)
EventTextFactory(
- event_id=user_org_event,
- iso="en",
- primary=True,
- description="This is a group",
- get_involved="Get involved!",
+ event_id=user_org_event, iso="en", primary=True
)
self.stdout.write(
self.style.ERROR(
- f"Number of users created: {n_users}\n"
- f"Number of organizations created: {n_users * n_orgs_per_user}\n"
- f"Number of groups created: {n_users * n_orgs_per_user * n_groups_per_org}\n"
- f"Number of events created: {n_users * n_orgs_per_user * n_events_per_org}\n"
+ 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"
)
)
- except Exception as error:
+ except TypeError as error:
self.stdout.write(
self.style.ERROR(
- f"An error occurred during the creation of dummy data: {error}"
+ f"A type error occurred during the creation of dummy data: {error}. Make sure to use dashes for populate_db arguments and that they're of the appropriate types."
)
)
diff --git a/backend/backend/settings.py b/backend/backend/settings.py
index c2f8f7237..94c45627a 100644
--- a/backend/backend/settings.py
+++ b/backend/backend/settings.py
@@ -174,7 +174,7 @@
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
],
- "DEFAULT_THROTTLE_RATES": {"anon": "7/min", "user": "10/min"},
+ "DEFAULT_THROTTLE_RATES": {"anon": "40/min", "user": "60/min"},
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"DEFAULT_PAGINATION_ORDERS_OBJECTS": False,
diff --git a/backend/content/serializers.py b/backend/content/serializers.py
index e8fe3e32d..77ba9550e 100644
--- a/backend/content/serializers.py
+++ b/backend/content/serializers.py
@@ -117,13 +117,13 @@ class Meta:
fields = "__all__"
def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int]]:
- if data["active"] is True and data["deprecation_date"] is not None:
+ if data["active"] is True and data.get("deprecation_date") is not None:
raise serializers.ValidationError(
_("Active topics cannot have a deprecation date."),
code="active_topic_with_deprecation_error",
)
- if data["active"] is False and data["deprecation_date"] is None:
+ if data["active"] is False and data.get("deprecation_date") is None:
raise serializers.ValidationError(
_("Deprecated topics must have a deprecation date."),
code="inactive_topic_no_deprecation_error",
diff --git a/backend/entities/factories.py b/backend/entities/factories.py
index b3892ba77..418645f72 100644
--- a/backend/entities/factories.py
+++ b/backend/entities/factories.py
@@ -103,9 +103,9 @@ class Meta:
group_id = factory.SubFactory(GroupFactory)
iso = factory.Faker("word")
primary = factory.Faker("boolean")
- description = factory.Faker("text")
- get_involved = factory.Faker("text")
- donate_prompt = factory.Faker("text")
+ description = factory.Faker(provider="text", locale="la", max_nb_chars=1000)
+ get_involved = factory.Faker(provider="text", locale="la")
+ donate_prompt = factory.Faker(provider="text", locale="la")
class GroupTopicFactory(factory.django.DjangoModelFactory):
@@ -190,9 +190,9 @@ class Meta:
org_id = factory.SubFactory(OrganizationFactory)
iso = "en"
primary = factory.Faker("boolean")
- description = factory.Faker("text")
- get_involved = factory.Faker("text")
- donate_prompt = factory.Faker("text")
+ description = factory.Faker(provider="text", locale="la", max_nb_chars=1000)
+ get_involved = factory.Faker(provider="text", locale="la")
+ donate_prompt = factory.Faker(provider="text", locale="la")
class OrganizationTopicFactory(factory.django.DjangoModelFactory):
diff --git a/backend/entities/models.py b/backend/entities/models.py
index 4dcd1d000..a3e4de22f 100644
--- a/backend/entities/models.py
+++ b/backend/entities/models.py
@@ -8,6 +8,7 @@
from django.db import models
from authentication import enums
+from utils.models import ISO_CHOICES
# MARK: Main Tables
@@ -129,7 +130,7 @@ def __str__(self) -> str:
class GroupText(models.Model):
group_id = models.ForeignKey(Group, on_delete=models.CASCADE)
- iso = models.CharField(max_length=2)
+ iso = models.CharField(max_length=2, choices=ISO_CHOICES)
primary = models.BooleanField(default=False)
description = models.TextField(max_length=500)
get_involved = models.TextField(max_length=500, blank=True)
@@ -216,7 +217,7 @@ def __str__(self) -> str:
class OrganizationText(models.Model):
org_id = models.ForeignKey(Organization, on_delete=models.CASCADE)
- iso = models.CharField(max_length=2)
+ iso = models.CharField(max_length=2, choices=ISO_CHOICES)
primary = models.BooleanField(default=False)
description = models.TextField(max_length=2500)
get_involved = models.TextField(max_length=500, blank=True)
diff --git a/backend/entities/serializers.py b/backend/entities/serializers.py
index afceeb74b..7f2ba3097 100644
--- a/backend/entities/serializers.py
+++ b/backend/entities/serializers.py
@@ -79,17 +79,19 @@ def validate(self, data: dict[str, Any]) -> dict[str, Any]:
raise serializers.ValidationError(
"You must accept the terms of service to create an organization."
)
- return data
+ return data
def create(self, validated_data: dict[str, Any]) -> Organization:
description = validated_data.pop("description", None)
org = Organization.objects.create(**validated_data)
+
if org and description:
org_text = OrganizationText.objects.create(
org_id=org, description=description
)
org.org_text = org_text
+
return org
diff --git a/backend/entities/views.py b/backend/entities/views.py
index 972b0d5f0..a4b7f1852 100644
--- a/backend/entities/views.py
+++ b/backend/entities/views.py
@@ -2,7 +2,7 @@
from django.utils import timezone
from rest_framework import status, viewsets
from rest_framework.authentication import TokenAuthentication
-from rest_framework.permissions import IsAuthenticated
+from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
@@ -60,20 +60,24 @@ class GroupViewSet(viewsets.ModelViewSet[Group]):
def list(self, request: Request, *args: str, **kwargs: int) -> Response:
serializer = self.get_serializer(self.queryset, many=True)
+
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request: Request) -> Response:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(created_by=request.user)
- data = {"message": f"New Group created: {serializer.data}"}
+ data = {"message": f"New group created: {serializer.data}"}
+
return Response(data, status=status.HTTP_201_CREATED)
def retrieve(self, request: Request, *args: str, **kwargs: int) -> Response:
- group = self.queryset.get(id=kwargs["pk"])
- serializer = self.get_serializer(group)
+ if group := self.queryset.get(id=kwargs["pk"]):
+ serializer = self.get_serializer(group)
- return Response(serializer.data, status=status.HTTP_200_OK)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ return Response({"error": "Group not found"}, status.HTTP_404_NOT_FOUND)
def partial_update(self, request: Request, *args: str, **kwargs: int) -> Response:
group = self.queryset.filter(id=kwargs["pk"]).first()
@@ -82,6 +86,7 @@ def partial_update(self, request: Request, *args: str, **kwargs: int) -> Respons
return Response(
{"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND
)
+
if request.user != group.created_by:
return Response(
{"error": "You are not authorized to update this group"},
@@ -91,6 +96,7 @@ def partial_update(self, request: Request, *args: str, **kwargs: int) -> Respons
serializer = self.get_serializer(group, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
+
return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request: Request, *args: str, **kwargs: int) -> Response:
@@ -100,12 +106,15 @@ def destroy(self, request: Request, *args: str, **kwargs: int) -> Response:
return Response(
{"error": "Group not found"}, status=status.HTTP_404_NOT_FOUND
)
+
if request.user != group.created_by:
return Response(
{"error": "You are not authorized to delete this group"},
status.HTTP_401_UNAUTHORIZED,
)
+
group.delete()
+
return Response(
{"message": "Group deleted successfully"}, status=status.HTTP_200_OK
)
@@ -116,31 +125,31 @@ class OrganizationViewSet(viewsets.ModelViewSet[Organization]):
serializer_class = OrganizationSerializer
pagination_class = CustomPagination
throttle_classes = [AnonRateThrottle, UserRateThrottle]
- permission_classes = [
- IsAuthenticated,
- ]
- authentication_classes = [
- TokenAuthentication,
- ]
+ permission_classes = [IsAuthenticatedOrReadOnly]
+ authentication_classes = [TokenAuthentication]
+
+ def list(self, request: Request) -> Response:
+ serializer = self.get_serializer(self.get_queryset(), many=True)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request: Request) -> Response:
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
org = serializer.save(created_by=request.user)
OrganizationApplication.objects.create(org_id=org)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
+ data = {"message": f"New organization created: {serializer.data}"}
+
+ return Response(data, status=status.HTTP_201_CREATED)
def retrieve(self, request: Request, pk: str | None = None) -> Response:
if org := self.queryset.filter(id=pk).first():
serializer = self.get_serializer(org)
+
return Response(serializer.data, status=status.HTTP_200_OK)
return Response({"error": "Organization not found"}, status.HTTP_404_NOT_FOUND)
- def list(self, request: Request) -> Response:
- serializer = self.get_serializer(self.get_queryset(), many=True)
- return Response(serializer.data, status=status.HTTP_200_OK)
-
def update(self, request: Request, pk: str | None = None) -> Response:
org = self.queryset.filter(id=pk).first()
if org is None:
@@ -157,6 +166,7 @@ def update(self, request: Request, pk: str | None = None) -> Response:
serializer = self.get_serializer(org, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
+
return Response(serializer.data, status.HTTP_200_OK)
def partial_update(self, request: Request, pk: str | None = None) -> Response:
@@ -175,6 +185,7 @@ def partial_update(self, request: Request, pk: str | None = None) -> Response:
serializer = self.get_serializer(org, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
+
return Response(serializer.data, status.HTTP_200_OK)
def destroy(self, request: Request, pk: str | None = None) -> Response:
diff --git a/backend/events/factories.py b/backend/events/factories.py
index c2aafa09d..f89b20e9f 100644
--- a/backend/events/factories.py
+++ b/backend/events/factories.py
@@ -23,11 +23,13 @@
class EventFactory(factory.django.DjangoModelFactory):
class Meta:
model = Event
+ django_get_or_create = ("created_by",)
name = factory.Faker("word")
tagline = factory.Faker("word")
- type = factory.Faker("word")
+ type = random.choice(["learn", "action"])
online_location_link = factory.Faker("url")
+ offline_location = factory.Faker("city")
offline_location_lat = factory.Faker("latitude")
offline_location_long = factory.Faker("longitude")
start_time = factory.LazyFunction(
@@ -142,8 +144,8 @@ class Meta:
event_id = factory.SubFactory(EventFactory)
iso = factory.Faker("word")
primary = factory.Faker("boolean")
- description = factory.Faker("text")
- get_involved = factory.Faker("text")
+ description = factory.Faker(provider="text", locale="la", max_nb_chars=1000)
+ get_involved = factory.Faker(provider="text", locale="la")
class EventTopicFactory(factory.django.DjangoModelFactory):
diff --git a/backend/events/models.py b/backend/events/models.py
index 936d8685b..5a9ea051b 100644
--- a/backend/events/models.py
+++ b/backend/events/models.py
@@ -8,6 +8,7 @@
from django.db import models
from backend.mixins.models import CreationDeletionMixin
+from utils.models import ISO_CHOICES
# MARK: Main Tables
@@ -26,6 +27,7 @@ class Event(CreationDeletionMixin):
)
type = models.CharField(max_length=255)
online_location_link = models.CharField(max_length=255, blank=True)
+ offline_location = models.CharField(max_length=255, blank=True)
offline_location_lat = models.FloatField(null=True, blank=True)
offline_location_long = models.FloatField(null=True, blank=True)
get_involved_url = models.URLField(blank=True)
@@ -35,6 +37,9 @@ class Event(CreationDeletionMixin):
is_private = models.BooleanField(default=False)
start_time = models.DateTimeField()
end_time = models.DateTimeField()
+ event_text = models.ForeignKey(
+ "EventText", on_delete=models.CASCADE, null=True, blank=True
+ )
def __str__(self) -> str:
return self.name
@@ -130,7 +135,7 @@ def __str__(self) -> str:
class EventText(models.Model):
event_id = models.ForeignKey(Event, on_delete=models.CASCADE)
- iso = models.CharField(max_length=2)
+ iso = models.CharField(max_length=2, choices=ISO_CHOICES)
primary = models.BooleanField()
description = models.TextField(max_length=500)
get_involved = models.TextField(max_length=500, blank=True)
diff --git a/backend/events/serializers.py b/backend/events/serializers.py
index 144c1f59a..5ecbd1024 100644
--- a/backend/events/serializers.py
+++ b/backend/events/serializers.py
@@ -2,7 +2,7 @@
Serializers for the events app.
"""
-from typing import Dict, Union
+from typing import Any, Dict, Union
from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext as _
@@ -31,10 +31,43 @@
# MARK: Main Tables
+class EventTextSerializer(serializers.ModelSerializer[EventText]):
+ class Meta:
+ model = EventText
+ fields = "__all__"
+
+
class EventSerializer(serializers.ModelSerializer[Event]):
+ event_text = EventTextSerializer(read_only=True)
+ description = serializers.CharField(write_only=True, required=False)
+
class Meta:
model = Event
- fields = "__all__"
+
+ extra_kwargs = {
+ "created_by": {"read_only": True},
+ "social_links": {"required": False},
+ "description": {"write_only": True},
+ }
+
+ fields = [
+ "id",
+ "name",
+ "tagline",
+ "icon_url",
+ "type",
+ "online_location_link",
+ "offline_location",
+ "offline_location_lat",
+ "offline_location_long",
+ "created_by",
+ "social_links",
+ "is_private",
+ "start_time",
+ "end_time",
+ "event_text",
+ "description",
+ ]
def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int]]:
if parse_datetime(data["start_time"]) > parse_datetime(data["end_time"]): # type: ignore
@@ -47,6 +80,18 @@ def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int
return data
+ def create(self, validated_data: dict[str, Any]) -> Event:
+ description = validated_data.pop("description", None)
+ event = Event.objects.create(**validated_data)
+
+ if event and description:
+ event_text = EventText.objects.create(
+ event_id=event, description=description
+ )
+ event.event_text = event_text
+
+ return event
+
class FormatSerializer(serializers.ModelSerializer[Event]):
class Meta:
@@ -115,12 +160,6 @@ class Meta:
fields = "__all__"
-class EventTextSerializer(serializers.ModelSerializer[EventText]):
- class Meta:
- model = EventText
- fields = "__all__"
-
-
class EventTopicSerializer(serializers.ModelSerializer[EventTopic]):
class Meta:
model = EventTopic
diff --git a/backend/events/views.py b/backend/events/views.py
index d1de501d4..1713a1c2b 100644
--- a/backend/events/views.py
+++ b/backend/events/views.py
@@ -1,4 +1,8 @@
-from rest_framework import viewsets
+from rest_framework import status, viewsets
+from rest_framework.authentication import TokenAuthentication
+from rest_framework.permissions import IsAuthenticatedOrReadOnly
+from rest_framework.request import Request
+from rest_framework.response import Response
from rest_framework.throttling import (
AnonRateThrottle,
UserRateThrottle,
@@ -41,6 +45,78 @@ class EventViewSet(viewsets.ModelViewSet[Event]):
serializer_class = EventSerializer
pagination_class = CustomPagination
throttle_classes = [AnonRateThrottle, UserRateThrottle]
+ permission_classes = [IsAuthenticatedOrReadOnly]
+ authentication_classes = [TokenAuthentication]
+
+ def list(self, request: Request, *args: str, **kwargs: int) -> Response:
+ serializer = self.get_serializer(self.get_queryset(), many=True)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ def create(self, request: Request, *args: str, **kwargs: int) -> Response:
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ serializer.save(created_by=request.user)
+ data = {"message": f"New event created: {serializer.data}"}
+
+ return Response(data, status=status.HTTP_201_CREATED)
+
+ def retrieve(self, request: Request, *args: str, **kwargs: int) -> Response:
+ if event := self.queryset.get(id=kwargs["pk"]):
+ serializer = self.get_serializer(event)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ return Response({"error": "Event not found"}, status.HTTP_404_NOT_FOUND)
+
+ def update(self, request: Request, *args: str, **kwargs: int) -> Response:
+ event = self.queryset.filter(id=kwargs["pk"]).first()
+ if event is None:
+ return Response({"error": "Event not found"}, status.HTTP_404_NOT_FOUND)
+
+ if request.user != event.created_by:
+ return Response(
+ {"error": "You are not authorized to update this event"},
+ status.HTTP_401_UNAUTHORIZED,
+ )
+
+ serializer = self.get_serializer(event, data=request.data, partial=True)
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+
+ return Response(serializer.data, status.HTTP_200_OK)
+
+ def partial_update(self, request: Request, *args: str, **kwargs: int) -> Response:
+ event = self.queryset.filter(id=kwargs["pk"]).first()
+ if event is None:
+ return Response({"error": "Event not found"}, status.HTTP_404_NOT_FOUND)
+
+ if request.user != event.created_by:
+ return Response(
+ {"error": "You are not authorized to update this event"},
+ status.HTTP_401_UNAUTHORIZED,
+ )
+
+ serializer = self.get_serializer(event, data=request.data, partial=True)
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+
+ return Response(serializer.data, status.HTTP_200_OK)
+
+ def destroy(self, request: Request, *args: str, **kwargs: int) -> Response:
+ event = self.queryset.filter(id=kwargs["pk"]).first()
+ if event is None:
+ return Response({"error": "Event not found"}, status.HTTP_404_NOT_FOUND)
+
+ if request.user != event.created_by:
+ return Response(
+ {"error": "You are not authorized to delete this event"},
+ status.HTTP_401_UNAUTHORIZED,
+ )
+
+ event.delete()
+
+ return Response({"message": "Event deleted successfully"}, status.HTTP_200_OK)
class FormatViewSet(viewsets.ModelViewSet[Format]):
diff --git a/backend/fixtures/superuser.json b/backend/fixtures/superuser.json
index 1b22a93a1..1b741b6fd 100644
--- a/backend/fixtures/superuser.json
+++ b/backend/fixtures/superuser.json
@@ -20,7 +20,7 @@
"email": "admin@activist.org",
"is_high_risk": false,
"is_active": true,
- "is_admin": false,
+ "is_admin": true,
"is_confirmed": true,
"groups": [],
"user_permissions": []
diff --git a/backend/fixtures/topics.json b/backend/fixtures/topics.json
new file mode 100644
index 000000000..00995272c
--- /dev/null
+++ b/backend/fixtures/topics.json
@@ -0,0 +1,98 @@
+[
+{
+ "model": "content.topic",
+ "pk": "24b970c6-5231-403b-9e9d-7de8375193ef",
+ "fields": {
+ "name": "Democracy",
+ "active": true,
+ "description": "Democracy",
+ "creation_date": "2024-09-21T20:21:47.121Z",
+ "last_updated": "2024-09-21T20:21:47.121Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "2c649964-f64c-4693-be31-0a9fdf981974",
+ "fields": {
+ "name": "Women's Rights",
+ "active": true,
+ "description": "Women's Rights",
+ "creation_date": "2024-09-21T20:24:41.462Z",
+ "last_updated": "2024-09-21T20:24:41.462Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "2d49b6e2-8916-4543-83f3-9782e8a9680a",
+ "fields": {
+ "name": "Animal Rights",
+ "active": true,
+ "description": "Animal Rights",
+ "creation_date": "2024-09-21T20:22:07.547Z",
+ "last_updated": "2024-09-21T20:22:07.547Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "b3c6c355-a084-4766-9963-3e54e1af3004",
+ "fields": {
+ "name": "Racial Justice",
+ "active": true,
+ "description": "Racial Justice",
+ "creation_date": "2024-09-21T20:24:28.226Z",
+ "last_updated": "2024-09-21T20:24:28.226Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "b8d8c9c2-e75b-4d49-aca7-c22cfb1f8b77",
+ "fields": {
+ "name": "Housing",
+ "active": true,
+ "description": "Housing",
+ "creation_date": "2024-09-21T20:22:46.519Z",
+ "last_updated": "2024-09-21T20:22:46.519Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "d13860c9-4467-4810-a2d5-7826f3769913",
+ "fields": {
+ "name": "Education",
+ "active": true,
+ "description": "Education",
+ "creation_date": "2024-09-21T20:22:23.446Z",
+ "last_updated": "2024-09-21T20:22:23.446Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "e3715e45-153d-4cce-b957-d68f5a6d4256",
+ "fields": {
+ "name": "Climate",
+ "active": true,
+ "description": "Climate",
+ "creation_date": "2024-09-21T20:20:33.450Z",
+ "last_updated": "2024-09-21T20:20:33.450Z",
+ "deprecation_date": null
+ }
+},
+{
+ "model": "content.topic",
+ "pk": "ec635e18-a89c-46d3-9a14-8f84be96af9b",
+ "fields": {
+ "name": "LGTBQIA+",
+ "active": true,
+ "description": "LGTBQIA+",
+ "creation_date": "2024-09-21T20:23:58.389Z",
+ "last_updated": "2024-09-21T20:23:58.389Z",
+ "deprecation_date": null
+ }
+}
+]
diff --git a/backend/utils/models.py b/backend/utils/models.py
new file mode 100644
index 000000000..e21207136
--- /dev/null
+++ b/backend/utils/models.py
@@ -0,0 +1,7 @@
+ISO_CHOICES = [
+ ("de", "de"),
+ ("en", "en"),
+ ("es", "es"),
+ ("fr", "fr"),
+ ("pt", "pt"),
+]
diff --git a/backend/utils/utils.py b/backend/utils/utils.py
index a73262c96..f757b2145 100644
--- a/backend/utils/utils.py
+++ b/backend/utils/utils.py
@@ -12,7 +12,7 @@ def validate_creation_and_deletion_dates(data: Any) -> None:
code="invalid_creation_date",
)
- if data["creation_date"] < data["deletion_date"]:
+ if data.get("deletion_date") and data.get("deletion_date") < data["creation_date"]:
raise serializers.ValidationError(
_("The field deletion_date cannot be before creation_date."),
code="invalid_date_order",
@@ -20,31 +20,11 @@ def validate_creation_and_deletion_dates(data: Any) -> None:
def validate_creation_and_deprecation_dates(data: Any) -> None:
- if data["deprecation_date"] < data["creation_date"]:
+ if (
+ data.get("deprecation_date")
+ and data.get("deprecation_date") < data["creation_date"]
+ ):
raise serializers.ValidationError(
_("The field deprecation_date cannot be before creation_date."),
code="invalid_date_order",
)
-
-
-def validate_flags_number(data: Any) -> None:
- if int(data["total_flags"]) < 0:
- raise serializers.ValidationError(
- _("The field total_flags cannot be negative."),
- code="negative_total_flags",
- )
-
-
-def validate_empty(value: Any, field_name: Any) -> None:
- if value == "" or value is None:
- raise serializers.ValidationError(
- _(f"The field {field_name} has to be filled."), code="empty_field"
- )
-
-
-def validate_object_existence(model: Any, object_id: Any) -> None:
- if model.objects.filter(id=object_id).exists():
- raise serializers.ValidationError(
- _(f"There is no {model.__name__} object with id {object_id}."),
- code="inexistent_object",
- )
diff --git a/docker-compose.yml b/docker-compose.yml
index cb53c28a4..9873891e0 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,8 @@ services:
python manage.py migrate &&
python manage.py loaddata fixtures/superuser.json &&
python manage.py loaddata fixtures/status_types.json &&
- python manage.py populate_db --users 10 --opu 1 --gpo 1 --epo 1 &&
+ 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 runserver 0.0.0.0:${BACKEND_PORT}"
ports:
- "${BACKEND_PORT}:${BACKEND_PORT}"
diff --git a/frontend/assets/css/tailwind.css b/frontend/assets/css/tailwind.css
index 76aa6984a..20319c392 100644
--- a/frontend/assets/css/tailwind.css
+++ b/frontend/assets/css/tailwind.css
@@ -131,6 +131,10 @@
@apply focus-brand elem-shadow-sm bg-light-layer-0 text-light-text hover:bg-light-highlight active:bg-light-layer-0 dark:border dark:border-dark-text dark:bg-dark-layer-0 dark:text-dark-text dark:hover:bg-dark-highlight dark:active:bg-dark-layer-0;
}
+ .style-btns-next-to-one-another {
+ @apply mx-auto grid max-w-[70%] grid-cols-1 gap-y-4 sm:mx-0 sm:max-w-[90%] sm:grid-cols-2 sm:grid-rows-1 sm:gap-x-4 sm:gap-y-0 md:max-w-[70%] md:gap-x-6 lg:max-w-[60%] xl:max-w-[70%] xl:gap-x-8 2xl:max-w-[80%];
+ }
+
.style-cta {
@apply focus-brand border border-light-text bg-light-cta-orange fill-light-text text-light-text hover:bg-light-cta-orange/80 active:bg-light-cta-orange dark:border-dark-cta-orange dark:bg-dark-cta-orange/10 dark:fill-dark-cta-orange dark:text-dark-cta-orange dark:hover:bg-dark-cta-orange/25 dark:active:bg-dark-cta-orange/10;
}
diff --git a/frontend/components/FriendlyCaptcha.vue b/frontend/components/FriendlyCaptcha.vue
index 1130246b8..1d528d7bb 100644
--- a/frontend/components/FriendlyCaptcha.vue
+++ b/frontend/components/FriendlyCaptcha.vue
@@ -7,7 +7,7 @@
}"
>
{
console.log("Captcha response:", response);
diff --git a/frontend/components/card/about/CardAboutGroup.vue b/frontend/components/card/about/CardAboutGroup.vue
index 5cb356e88..12e1de7ff 100644
--- a/frontend/components/card/about/CardAboutGroup.vue
+++ b/frontend/components/card/about/CardAboutGroup.vue
@@ -91,7 +91,10 @@ const props = defineProps<{
const res = await useAsyncData(
async () =>
- await fetchWithToken(`/entities/group_texts?group_id=${props.group.id}`, {})
+ await fetchWithOptionalToken(
+ `/entities/group_texts?group_id=${props.group.id}`,
+ {}
+ )
);
const groupTexts = res.data as unknown as GroupText[];
diff --git a/frontend/components/card/search-result/CardSearchResult.vue b/frontend/components/card/search-result/CardSearchResult.vue
index 3e7dc83cf..73b757805 100644
--- a/frontend/components/card/search-result/CardSearchResult.vue
+++ b/frontend/components/card/search-result/CardSearchResult.vue
@@ -210,11 +210,11 @@
-
{{ description }}
-
+
diff --git a/frontend/components/header/HeaderWebsite.vue b/frontend/components/header/HeaderWebsite.vue
index 928d680c5..46b0a9381 100644
--- a/frontend/components/header/HeaderWebsite.vue
+++ b/frontend/components/header/HeaderWebsite.vue
@@ -52,7 +52,47 @@
+
+
+
+
{{ $t(text) }}
-
+
+
+
+
+
diff --git a/frontend/components/page/PageCommunityFooter.vue b/frontend/components/page/PageCommunityFooter.vue
index 77bc2dafd..4dc1375ca 100644
--- a/frontend/components/page/PageCommunityFooter.vue
+++ b/frontend/components/page/PageCommunityFooter.vue
@@ -88,9 +88,7 @@
-
+
diff --git a/frontend/composables/fetch.ts b/frontend/composables/fetch.ts
index cb895551a..238be4195 100644
--- a/frontend/composables/fetch.ts
+++ b/frontend/composables/fetch.ts
@@ -1,15 +1,30 @@
-export const fetchWithToken = async (
+/**
+ * Returns data given the authentication status of the user.
+ * @param url Backend URL to make the request to.
+ * @param data Data to be returned.
+ * @returns The resulting data from the table.
+ */
+export const fetchWithOptionalToken = async (
url: string,
data: object | {} | undefined
) => {
const token = localStorage.getItem("accessToken");
- const res = await $fetch.raw(BASE_BACKEND_URL + url, {
- data,
- headers: {
- Authorization: `Token ${token}`,
- },
- });
+ if (token) {
+ const res = await $fetch.raw(BASE_BACKEND_URL + url, {
+ data,
+ headers: {
+ Authorization: `Token ${token}`,
+ },
+ });
- return res._data;
+ return res._data;
+ } else {
+ const res = await $fetch.raw(BASE_BACKEND_URL + url, {
+ data,
+ headers: {},
+ });
+
+ return res._data;
+ }
};
diff --git a/frontend/i18n/en-US.json b/frontend/i18n/en-US.json
index 098141e2e..59bf2a1b5 100644
--- a/frontend/i18n/en-US.json
+++ b/frontend/i18n/en-US.json
@@ -248,6 +248,10 @@
"components.landing_splash.message_1": "A platform for growing our movements and organizing actions.",
"components.landing_splash.message_2": "Free, open-source, privacy-focused and governed by our community.",
"components.landing_splash.request_access_aria_label": "Request access to activist.org",
+ "components.landing_splash.view_events": "View events",
+ "components.landing_splash.view_events_aria_label": "View the events section of the activist platform",
+ "components.landing_splash.view_organizations": "View organizations",
+ "components.landing_splash.view_organizations_aria_label": "View the organizations section of the activist platform",
"components.landing_tech_banner.open_header": "Open",
"components.landing_tech_banner.open_source_tagline": "Our code and processes",
"components.landing_tech_banner.open_source_text": "We're dedicated to working in the open to build trust with our partner organizations and fellow activists. All who want to help us build are welcome!",
diff --git a/frontend/pages/events/index.vue b/frontend/pages/events/index.vue
index a70d5c430..810315598 100644
--- a/frontend/pages/events/index.vue
+++ b/frontend/pages/events/index.vue
@@ -13,23 +13,18 @@
-
-
+
diff --git a/frontend/pages/organizations/[id]/groups/[id]/about.vue b/frontend/pages/organizations/[id]/groups/[id]/about.vue
index 81dbfc9c3..a8b5c667c 100644
--- a/frontend/pages/organizations/[id]/groups/[id]/about.vue
+++ b/frontend/pages/organizations/[id]/groups/[id]/about.vue
@@ -97,9 +97,12 @@ const aboveLargeBP = useBreakpoint("lg");
const { id } = useRoute().params;
const [resOrg, resOrgTexts] = await Promise.all([
- useAsyncData(async () => await fetchWithToken(`/entities/groups/${id}`, {})),
useAsyncData(
- async () => await fetchWithToken(`/entities/group_texts?org_id=${id}`, {})
+ async () => await fetchWithOptionalToken(`/entities/groups/${id}`, {})
+ ),
+ useAsyncData(
+ async () =>
+ await fetchWithOptionalToken(`/entities/group_texts?org_id=${id}`, {})
),
]);
diff --git a/frontend/stores/dev.ts b/frontend/stores/dev.ts
new file mode 100644
index 000000000..a7ac6d6d8
--- /dev/null
+++ b/frontend/stores/dev.ts
@@ -0,0 +1,14 @@
+import { useLocalStorage } from "@vueuse/core";
+import { defineStore } from "pinia";
+
+export const useDevMode = defineStore("devMode", {
+ state: () => ({
+ active: useLocalStorage("active", false),
+ }),
+
+ actions: {
+ check() {
+ this.active = window.location.href.includes("localhost:3000");
+ },
+ },
+});
diff --git a/frontend/stores/event.ts b/frontend/stores/event.ts
index 5ca47ef40..0ec3ff0b1 100644
--- a/frontend/stores/event.ts
+++ b/frontend/stores/event.ts
@@ -1,7 +1,10 @@
import type {
Event,
+ EventText,
PiniaResEvent,
+ PiniaResEvents,
PiniaResEventText,
+ PiniaResEventTexts,
} from "~/types/events/event";
interface EventStore {
@@ -23,18 +26,23 @@ export const useEventStore = defineStore("event", {
createdBy: "",
iconURL: "",
type: "learn",
+ onlineLocationLink: "",
offlineLocation: "",
+ offlineLocationLat: "",
+ offlineLocationLong: "",
getInvolvedURL: "",
socialLinks: [""],
startTime: "",
+ endTime: "",
+ creationDate: "",
- // event_organizations
organizations: [],
- // event_text
+ eventTextID: "",
description: "",
getInvolved: "",
},
+
events: [],
}),
actions: {
@@ -49,25 +57,28 @@ export const useEventStore = defineStore("event", {
const [resEvent, resEventTexts] = await Promise.all([
useAsyncData(
- async () => await fetchWithToken(`/entities/events/${id}`, {})
+ async () => await fetchWithOptionalToken(`/events/events/${id}`, {})
),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/event_faq?event_id=${id}`,
// {}
// )
// ),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/event_resources?event_id=${id}`,
// {}
// )
// ),
useAsyncData(
async () =>
- await fetchWithToken(`/entities/event_texts?event_id=${id}`, {})
+ await fetchWithOptionalToken(
+ `/events/event_texts?event_id=${id}`,
+ {}
+ )
),
]);
@@ -98,7 +109,69 @@ export const useEventStore = defineStore("event", {
// MARK: Fetch All
- async fetchAll() {},
+ async fetchAll() {
+ this.loading = true;
+
+ const [responseEvents] = await Promise.all([
+ useAsyncData(
+ async () => await fetchWithOptionalToken(`/events/events/`, {})
+ ),
+ ]);
+
+ const events = responseEvents.data as unknown as PiniaResEvents;
+
+ console.log(`Here: ${JSON.stringify(events._value)}`);
+
+ if (events._value) {
+ const responseEventTexts = (await Promise.all(
+ events._value.map((event) =>
+ useAsyncData(
+ async () =>
+ await fetchWithOptionalToken(
+ `/events/event_texts?event_id=${event.id}`,
+ {}
+ )
+ )
+ )
+ )) as unknown as PiniaResEventTexts[];
+
+ const eventTextsData = responseEventTexts.map(
+ (text) => text.data._value.results[0]
+ ) as unknown as EventText[];
+
+ const eventsWithTexts = events._value.map(
+ (event: Event, index: number) => {
+ const texts = eventTextsData[index];
+ return {
+ id: event.id,
+ name: event.name,
+ tagline: event.tagline,
+ createdBy: event.createdBy,
+ iconURL: event.iconURL,
+ type: event.type,
+ onlineLocationLink: event.onlineLocationLink,
+ offlineLocation: event.offlineLocation,
+ offlineLocationLat: event.offlineLocationLat,
+ offlineLocationLong: event.offlineLocationLong,
+ getInvolvedURL: event.getInvolvedURL,
+ socialLinks: event.socialLinks,
+ startTime: event.startTime,
+ endTime: event.endTime,
+ creationDate: event.creationDate,
+ organizations: event.organizations,
+
+ eventTextID: event.eventTextID,
+ description: texts.description,
+ getInvolved: texts.getInvolved,
+ };
+ }
+ );
+
+ this.events = eventsWithTexts;
+ }
+
+ this.loading = false;
+ },
// MARK: Update
@@ -106,6 +179,10 @@ export const useEventStore = defineStore("event", {
// MARK: Delete
- async delete() {},
+ async delete(id: string | undefined) {
+ this.loading = true;
+
+ this.loading = false;
+ },
},
});
diff --git a/frontend/stores/group.ts b/frontend/stores/group.ts
index 081ba8bfb..74e312dbb 100644
--- a/frontend/stores/group.ts
+++ b/frontend/stores/group.ts
@@ -34,29 +34,35 @@ export const useGroupStore = defineStore("group", {
const [resGroup, resGroupOrg, resGroupTexts] = await Promise.all([
useAsyncData(
- async () => await fetchWithToken(`/entities/groups/${id}`, {})
+ async () => await fetchWithOptionalToken(`/entities/groups/${id}`, {})
),
useAsyncData(
async () =>
- await fetchWithToken(`/entities/organizations?group_id=${id}`, {})
+ await fetchWithOptionalToken(
+ `/entities/organizations?group_id=${id}`,
+ {}
+ )
),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/group_faq?group_id=${id}`,
// {}
// )
// ),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/group_resources?group_id=${id}`,
// {}
// )
// ),
useAsyncData(
async () =>
- await fetchWithToken(`/entities/group_texts?group_id=${id}`, {})
+ await fetchWithOptionalToken(
+ `/entities/group_texts?group_id=${id}`,
+ {}
+ )
),
]);
diff --git a/frontend/stores/modals.ts b/frontend/stores/modals.ts
index d47fe7f22..d5578ae6d 100644
--- a/frontend/stores/modals.ts
+++ b/frontend/stores/modals.ts
@@ -11,7 +11,7 @@ export const useModals = defineStore("modals", {
actions: {
openModal(modalName: string) {
- const modals = this.modals;
+ const { modals } = this;
for (const key in modals) {
modals[key].isOpen = false;
}
diff --git a/frontend/stores/organization.ts b/frontend/stores/organization.ts
index 1dbdbd9d9..b0e9620ea 100644
--- a/frontend/stores/organization.ts
+++ b/frontend/stores/organization.ts
@@ -33,7 +33,7 @@ export const useOrganizationStore = defineStore("organization", {
status: 1,
groups: [],
- organization_text_id: "",
+ organizationTextID: "",
description: "",
getInvolved: "",
donationPrompt: "",
@@ -89,28 +89,29 @@ export const useOrganizationStore = defineStore("organization", {
const [responseOrg, responseOrgTexts] = await Promise.all([
useAsyncData(
- async () => await fetchWithToken(`/entities/organizations/${id}`, {})
+ async () =>
+ await fetchWithOptionalToken(`/entities/organizations/${id}`, {})
),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/organization_faq?org_id=${id}`,
// {}
// )
// ),
// useAsyncData(
- // async () => await fetchWithToken(`/entities/groups?org_id=${id}`, {})
+ // async () => await fetchWithOptionalToken(`/entities/groups?org_id=${id}`, {})
// ),
// useAsyncData(
// async () =>
- // await fetchWithToken(
+ // await fetchWithOptionalToken(
// `/entities/organization_resources?org_id=${id}`,
// {}
// )
// ),
useAsyncData(
async () =>
- await fetchWithToken(
+ await fetchWithOptionalToken(
`/entities/organization_texts?org_id=${id}`,
{}
)
@@ -144,7 +145,7 @@ export const useOrganizationStore = defineStore("organization", {
this.organization.description = texts.description;
this.organization.getInvolved = texts.getInvolved;
this.organization.donationPrompt = texts.donationPrompt;
- this.organization.organization_text_id = texts.id;
+ this.organization.organizationTextID = texts.id;
this.loading = false;
},
@@ -156,7 +157,8 @@ export const useOrganizationStore = defineStore("organization", {
const [responseOrgs] = await Promise.all([
useAsyncData(
- async () => await fetchWithToken(`/entities/organizations/`, {})
+ async () =>
+ await fetchWithOptionalToken(`/entities/organizations/`, {})
),
]);
@@ -167,7 +169,7 @@ export const useOrganizationStore = defineStore("organization", {
orgs._value.map((org) =>
useAsyncData(
async () =>
- await fetchWithToken(
+ await fetchWithOptionalToken(
`/entities/organization_texts?org_id=${org.id}`,
{}
)
@@ -179,8 +181,6 @@ export const useOrganizationStore = defineStore("organization", {
(text) => text.data._value.results[0]
) as unknown as OrganizationText[];
- console.log(`Here: ${JSON.stringify(orgTextsData)}`);
-
const organizationsWithTexts = orgs._value.map(
(organization: Organization, index: number) => {
const texts = orgTextsData[index];
@@ -195,7 +195,8 @@ export const useOrganizationStore = defineStore("organization", {
socialLinks: organization.socialLinks,
status: organization.status,
groups: organization.groups,
- organization_text_id: organization.organization_text_id,
+
+ organizationTextID: organization.organizationTextID,
description: texts.description,
getInvolved: texts.getInvolved,
donationPrompt: texts.donationPrompt,
@@ -235,7 +236,7 @@ export const useOrganizationStore = defineStore("organization", {
const responseOrgTexts = await $fetch(
BASE_BACKEND_URL +
- `/entities/organization_texts/${org.organization_text_id}/`,
+ `/entities/organization_texts/${org.organizationTextID}/`,
{
method: "PUT",
body: {
diff --git a/frontend/types/entities/organization.d.ts b/frontend/types/entities/organization.d.ts
index 83f0d2d1d..42380eeb5 100644
--- a/frontend/types/entities/organization.d.ts
+++ b/frontend/types/entities/organization.d.ts
@@ -36,7 +36,7 @@ export interface Organization {
// organization_task
// task?: Task[];
- organization_text_id: string;
+ organizationTextID: string;
description: string;
getInvolved: string;
donationPrompt: string;
diff --git a/frontend/types/events/event.d.ts b/frontend/types/events/event.d.ts
index 5cec4c85a..2bb67e826 100644
--- a/frontend/types/events/event.d.ts
+++ b/frontend/types/events/event.d.ts
@@ -30,7 +30,7 @@ export interface Event {
// event_task
// task?: Task[];
- // event_text
+ eventTextID: string;
description: string;
getInvolved: string;
@@ -100,6 +100,13 @@ export interface PiniaResEvent {
_value: Event;
}
+export interface PiniaResEvents {
+ __v_isShallow: boolean;
+ __v_isRef: boolean;
+ _rawValue: Event[];
+ _value: Event[];
+}
+
export interface PiniaResEventText {
__v_isShallow: boolean;
__v_isRef: boolean;
@@ -116,3 +123,39 @@ export interface PiniaResEventText {
results: EventText[];
};
}
+
+export interface PiniaResEventTexts {
+ data: {
+ __v_isShallow: boolean;
+ __v_isRef: boolean;
+ _rawValue: {
+ count: number;
+ next: null;
+ previous: null;
+ results: EventText[];
+ };
+ _value: {
+ count: number;
+ next: null;
+ previous: null;
+ results: EventText[];
+ };
+ };
+ pending: {
+ __v_isShallow: boolean;
+ __v_isRef: boolean;
+ _rawValue: boolean;
+ _value: boolean;
+ };
+ error: {
+ _object: { [$key: string]: null };
+ _key: string;
+ __v_isRef: boolean;
+ };
+ status: {
+ __v_isShallow: boolean;
+ __v_isRef: boolean;
+ _rawValue: string;
+ _value: string;
+ };
+}
diff --git a/frontend/utils/testEntities.ts b/frontend/utils/testEntities.ts
index 3d805dfac..4b5bcd49a 100644
--- a/frontend/utils/testEntities.ts
+++ b/frontend/utils/testEntities.ts
@@ -48,7 +48,7 @@ export const testClimateOrg: Organization = {
groups: ["Fundraising", "Campaigning"],
socialLinks: ["climate-org@mastodon", "climate-org@email"],
iconURL: "URL/for/image",
- organization_text_id: "06cb36a3-13c5-4518-b676-33ec734744ed",
+ organizationTextID: "06cb36a3-13c5-4518-b676-33ec734744ed",
description: "Testing how organizations work",
getInvolved: "Hey, get involved!",
donationPrompt: "Hey thanks!",
@@ -119,7 +119,7 @@ export const testTechOrg: Organization = {
groups: [testTechGroup1, testTechGroup2],
socialLinks: ["tfb@mastodon", "tfb@email"],
// donationPrompt: "Hey thanks!",
- organization_text_id: "06cb36a3-13c5-4518-b676-33ec734744ed",
+ organizationTextID: "06cb36a3-13c5-4518-b676-33ec734744ed",
description: "Testing how organizations work",
getInvolved: "Hey, get involved!",
donationPrompt: "Hey thanks!",
@@ -145,6 +145,7 @@ export const testClimateEvent: Event = {
// supportingUsers: [user, user],
// iconURL: "/images/an_image.svg",
socialLinks: ["climate_org@mastodon", "climate_org@email.com"],
+ eventTextID: "",
};
export const testTechEvent: Event = {
@@ -162,6 +163,7 @@ export const testTechEvent: Event = {
startTime: new Date().toLocaleDateString(),
// supportingUsers: [user, user, user],
socialLinks: [""],
+ eventTextID: "",
};
export const testResource: Resource = {