diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2142b0e13..43a2b65ad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -191,7 +191,7 @@ git remote add upstream https://github.com/activist-org/activist.git 4. Start your docker images with the following: ```bash - # --build only necessary with new dependencies or backend model changes + # --build only necessary with new dependencies or backend model changes. docker compose --env-file .env.dev up --build # And to stop the containers when you're done working: diff --git a/backend/authentication/serializers.py b/backend/authentication/serializers.py index 8c0b45f56..44387cc6a 100644 --- a/backend/authentication/serializers.py +++ b/backend/authentication/serializers.py @@ -106,6 +106,10 @@ class Meta: # MARK: Methods +class DeleteUserResponseSerializer(serializers.Serializer[UserModel]): + message = serializers.CharField(max_length=200) + + class SignupSerializer(serializers.ModelSerializer[UserModel]): password_confirmed = serializers.CharField(write_only=True) diff --git a/backend/authentication/views.py b/backend/authentication/views.py index 32969b555..a0dbee6cc 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -26,6 +26,7 @@ UserTopic, ) from .serializers import ( + DeleteUserResponseSerializer, LoginSerializer, PasswordResetSerializer, SignupSerializer, @@ -239,6 +240,7 @@ def post(self, request: Request) -> Response: class DeleteUserView(APIView): queryset = UserModel.objects.all() permission_classes = (IsAuthenticated,) + serializer_class = DeleteUserResponseSerializer def delete(self, request: Request, pk: UUID | str) -> Response: user = UserModel.objects.get(pk=pk) diff --git a/backend/backend/settings.py b/backend/backend/settings.py index 94c45627a..7faf23317 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -13,12 +13,10 @@ import django_stubs_ext import dotenv -from rest_framework import viewsets -from rest_framework.settings import api_settings -django_stubs_ext.monkeypatch(extra_classes=(viewsets.ModelViewSet,)) dotenv.load_dotenv() +# MARK: DB DATABASE_HOST = os.getenv("DATABASE_HOST") DATABASE_NAME = os.getenv("DATABASE_NAME") @@ -45,26 +43,29 @@ "DJANGO_CSRF_TRUSTED_ORIGINS", "http://localhost" ).split(" ") -# Application definition +# MARK: Apps INSTALLED_APPS = [ - "rest_framework", - "rest_framework.authtoken", + "corsheaders", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "djangorestframework_camel_case", + "drf_spectacular", + "rest_framework", + "rest_framework.authtoken", "backend", "authentication", "entities", "content", "events", - "drf_spectacular", - "corsheaders", ] +# MARK: Middleware + MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", @@ -75,14 +76,19 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", + "djangorestframework_camel_case.middleware.CamelCaseMiddleWare", ] +# MARK: URLs + CORS_ALLOWED_ORIGINS = [ "http://localhost:3000", ] ROOT_URLCONF = "backend.urls" +# MARK: Templates + TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", @@ -100,7 +106,7 @@ ] -# Database +# MARK: Database # https://docs.djangoproject.com/en/4.2/ref/settings/#databases DATABASES = { @@ -115,7 +121,7 @@ } -# Password validation +# MARK: Pass Validation # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ @@ -135,7 +141,7 @@ AUTH_USER_MODEL = "authentication.UserModel" -# Internationalization +# MARK: I18n # https://docs.djangoproject.com/en/4.2/topics/i18n/ LANGUAGE_CODE = "en-us" @@ -147,18 +153,18 @@ USE_TZ = True -# Static files (CSS, JavaScript, Images) +# MARK: Static Files # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_ROOT = BASE_DIR / "static/" STATIC_URL = "static/" -# Default primary key field type +# MARK: Primary Key # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" -# Email Settings +# MARK: Email # https://docs.djangoproject.com/en/5.0/topics/email/ EMAIL_HOST = os.getenv("EMAIL_HOST") @@ -169,6 +175,8 @@ # DEVELOPMENT ONLY EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +# MARK: REST Framework + REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", @@ -183,19 +191,33 @@ "rest_framework.authentication.TokenAuthentication", ], "EXCEPTION_HANDLER": "backend.exception_handler.bad_request_logger", + "DEFAULT_RENDERER_CLASSES": ( + "djangorestframework_camel_case.render.CamelCaseJSONRenderer", + "djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer", + ), + "DEFAULT_PARSER_CLASSES": ( + "djangorestframework_camel_case.parser.CamelCaseFormParser", + "djangorestframework_camel_case.parser.CamelCaseMultiPartParser", + "djangorestframework_camel_case.parser.CamelCaseJSONParser", + ), } +# MARK: Spectacular + SPECTACULAR_SETTINGS = { "TITLE": "activist.org API", - "DESCRIPTION": "Open-source, nonprofit activism platform", + "DESCRIPTION": "An open-source activism platform", "VERSION": "0.1.0", "SERVE_INCLUDE_SCHEMA": False, + "CAMELIZE_NAMES": False, + "POSTPROCESSING_HOOKS": [ + "drf_spectacular.hooks.postprocess_schema_enums", + "drf_spectacular.contrib.djangorestframework_camel_case.camelize_serializer_fields", + ], + "SWAGGER_UI_FAVICON_HREF": "https://github.com/activist-org/activist/blob/main/backend/static/swagger_favicon.png?raw=true", } -# Workaround #471 / monkeypatch() is overriding the REST_FRAMEWORK dict. -api_settings.reload() - -# Logging Configuration +# MARK: Logging # https://docs.djangoproject.com/en/4.2/topics/logging/ LOGGING = { @@ -225,3 +247,14 @@ }, }, } + +# MARK: API Settings + +from rest_framework import viewsets # noqa: E402 + +django_stubs_ext.monkeypatch(extra_classes=(viewsets.ModelViewSet,)) + +from rest_framework.settings import api_settings # noqa: E402 + +# Workaround #471 / monkeypatch() is overriding the REST_FRAMEWORK dict. +api_settings.reload() diff --git a/backend/entities/admin.py b/backend/entities/admin.py index eb615f76d..b89219522 100644 --- a/backend/entities/admin.py +++ b/backend/entities/admin.py @@ -43,19 +43,19 @@ # MARK: Methods -class GroupAdmin(admin.ModelAdmin): +class GroupAdmin(admin.ModelAdmin[Group]): list_display = ["group_name", "name"] -class GroupTextAdmin(admin.ModelAdmin): +class GroupTextAdmin(admin.ModelAdmin[GroupText]): list_display = ["id", "group_id"] -class OrganizationAdmin(admin.ModelAdmin): +class OrganizationAdmin(admin.ModelAdmin[Organization]): list_display = ["org_name", "name"] -class OrganizationTextAdmin(admin.ModelAdmin): +class OrganizationTextAdmin(admin.ModelAdmin[OrganizationText]): list_display = ["id", "org_id"] diff --git a/backend/entities/factories.py b/backend/entities/factories.py index 418645f72..9aa15fdbd 100644 --- a/backend/entities/factories.py +++ b/backend/entities/factories.py @@ -33,6 +33,7 @@ class Meta: name = factory.Faker("word") tagline = factory.Faker("word") social_links = ["https://www.instagram.com/activist_org/"] + get_involved_url = "https://activist.org/" created_by = factory.SubFactory("authentication.factories.UserFactory") terms_checked = factory.Faker("boolean") status = factory.SubFactory("entities.factories.StatusTypeFactory", name="Active") diff --git a/backend/entities/serializers.py b/backend/entities/serializers.py index bff553d89..418ccb27a 100644 --- a/backend/entities/serializers.py +++ b/backend/entities/serializers.py @@ -40,7 +40,8 @@ class Meta: class OrganizationTextSerializer(serializers.ModelSerializer[OrganizationText]): - orgID = serializers.StringRelatedField(source="org_id.id") + # mypy thinks a generic type argument is needed for StringRelatedField. + org_id = serializers.StringRelatedField(source="org_id.id") # type: ignore[var-annotated] class Meta: model = OrganizationText diff --git a/backend/requirements-dev.in b/backend/requirements-dev.in index ded40e4a2..eae79888f 100644 --- a/backend/requirements-dev.in +++ b/backend/requirements-dev.in @@ -1,13 +1,13 @@ # In order to add, delete or modify a dependency, please update -# the reference here and then run: +# the reference here and then the following in ./backend: # -# - `pip-compile --rebuild requirements.in` -# - `pip-compile --rebuild requirements-dev.in` +# - pip-compile --rebuild requirements.in +# - pip-compile --rebuild requirements-dev.in # # Make sure we use production deps for constraining installed dev packages. This # is important as otherwise we could be running tests with different versions # than production. -# + -r requirements.txt factory-boy diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index f4b84e324..b6d74f671 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -53,6 +53,8 @@ djangorestframework==3.15.2 # via # -r requirements.txt # drf-spectacular +djangorestframework-camel-case==1.4.2 + # via -r requirements.txt djangorestframework-stubs==3.14.4 # via -r requirements.txt drf-spectacular==0.26.5 diff --git a/backend/requirements.in b/backend/requirements.in index 21db9d92b..b9e0e8e27 100644 --- a/backend/requirements.in +++ b/backend/requirements.in @@ -1,15 +1,15 @@ # In order to add, delete or modify a dependency, please update -# the reference here and then run: -# -# - `pip-compile --rebuild requirements.in` -# - `pip-compile --rebuild requirements-dev.in` +# the reference here and then run the following in ./backend: # +# - pip-compile --rebuild requirements.in +# - pip-compile --rebuild requirements-dev.in Django django-cors-headers django-stubs django-stubs-ext djangorestframework +djangorestframework-camel-case djangorestframework-stubs drf-spectacular gunicorn diff --git a/backend/requirements.txt b/backend/requirements.txt index d156f5ed7..8f85ff399 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -36,6 +36,8 @@ djangorestframework==3.15.2 # via # -r requirements.in # drf-spectacular +djangorestframework-camel-case==1.4.2 + # via -r requirements.in djangorestframework-stubs==3.14.4 # via -r requirements.in drf-spectacular==0.26.5 diff --git a/frontend/assets/css/dark.css b/frontend/assets/css/dark.css new file mode 100644 index 000000000..e91f19ad7 --- /dev/null +++ b/frontend/assets/css/dark.css @@ -0,0 +1,21 @@ +dark: { + "layer-0":rgba(6, 8, 15, 1); + "layer-1":rgba(19, 19, 22, 1); + "layer-2":rgba(22, 27, 34, 1); + "section-div":rgba(43, 50, 59, 1); + "primary-text":rgba(255, 255, 255, 0.8); + "primary-text-over-layer-2":rgba(208, 209, 211, 1); + "distinct-text":rgba(150, 150, 150, 0.85); + "distinct-text-over-layer-2":rgba(131, 132, 133, 1); + "link-text":rgba(86, 167, 252, 0.9); + "link-text-hover":rgba(134, 192, 253, 0.9); + "menu-selection":rgba(200, 200, 200, 1); + interactive: rgba(133, 126, 123, 1); + "highlight":rgba(70, 70, 70, 0.25); + "highlight-lighter":rgba(120, 120, 120, 0.50); + "cta-orange":rgba(241, 156, 65, 1); + "action-red":rgba(238, 90, 88, 1); + "learn-blue":rgba(62, 146, 204, 1); + "accepted-green":rgba(97, 139, 37, 1); + "warn-yellow":rgba(255, 209, 102, 1); +} diff --git a/frontend/assets/css/light.css b/frontend/assets/css/light.css new file mode 100644 index 000000000..61000fc5d --- /dev/null +++ b/frontend/assets/css/light.css @@ -0,0 +1,21 @@ +light: { + "layer-0":rgba(255, 255, 255, 1); + "layer-1":rgba(246, 248, 250, 1); + "layer-2":rgba(240, 240, 235, 1); + "section-div":rgba(216, 222, 228, 1); + "primary-text":rgba(0, 0, 0, 0.85); + "primary-text-over-layer-2":rgba(36, 36, 35, 1); + "distinct-text":rgba(90, 90, 90, 0.9); + "distinct-text-over-layer-2":rgba(105, 105, 105, 1); + "link-text":rgba(0, 92, 184, 0.9); + "link-text-hover":rgba(0, 59, 119, 0.9); + "menu-selection":rgba(50, 50, 50, 1); + interactive: rgba(75, 75, 67, 1); + "highlight":rgba(140, 140, 140, 0.20); + "highlight-lighter":rgba(120, 120, 120, 0.30); + "cta-orange":rgba(242, 166, 84, 1); + "action-red":rgba(186, 61, 59, 1); + "learn-blue":rgba(33, 118, 174, 1); + "accepted-green":rgba(62, 137, 20, 1); + "warn-yellow":rgba(255, 191, 0, 1); +} diff --git a/frontend/components/EmptyState.vue b/frontend/components/EmptyState.vue index f975a8e53..bf54adc44 100644 --- a/frontend/components/EmptyState.vue +++ b/frontend/components/EmptyState.vue @@ -3,7 +3,7 @@ class="flex w-full flex-col items-center bg-light-layer-0 text-light-text dark:bg-dark-layer-0 dark:text-dark-text" >
diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index 89649d9dc..35f886d0b 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -120,11 +120,11 @@ const props = defineProps<{ }>(); const { userIsSignedIn } = useUser(); -const paramsID = useRoute().params.id; -const paramsIDGroup = useRoute().params.groupID; +const paramsId = useRoute().params.id; +const paramsIdGroup = useRoute().params.groupId; -const id = typeof paramsID === "string" ? paramsID : undefined; -const idGroup = typeof paramsIDGroup === "string" ? paramsIDGroup : undefined; +const id = typeof paramsId === "string" ? paramsId : undefined; +const idGroup = typeof paramsIdGroup === "string" ? paramsIdGroup : undefined; const organizationStore = useOrganizationStore(); const groupStore = useGroupStore(); @@ -135,13 +135,13 @@ let group: Group; let event: Event; if (props.pageType == "organization") { - await organizationStore.fetchByID(id); + await organizationStore.fetchById(id); organization = organizationStore.organization; } else if (props.pageType == "group") { - await groupStore.fetchByID(idGroup); + await groupStore.fetchById(idGroup); group = groupStore.group; } else if (props.pageType == "event") { - await eventStore.fetchByID(id); + await eventStore.fetchById(id); event = eventStore.event; } diff --git a/frontend/components/card/CardOrgApplicationVote.vue b/frontend/components/card/CardOrgApplicationVote.vue index fcae4242d..3e447e23e 100644 --- a/frontend/components/card/CardOrgApplicationVote.vue +++ b/frontend/components/card/CardOrgApplicationVote.vue @@ -7,7 +7,7 @@ class="mr-5 fill-light-text dark:fill-dark-text" > @@ -16,7 +16,7 @@ class="rounded border border-light-section-div dark:border-dark-section-div" >
@@ -49,7 +49,7 @@

-
+

{{ organization.getInvolved }}

@@ -90,7 +90,7 @@ const idParam = useRoute().params.id; const id = typeof idParam === "string" ? idParam : undefined; const organizationStore = useOrganizationStore(); -await organizationStore.fetchByID(id); +await organizationStore.fetchById(id); const { organization } = organizationStore; diff --git a/frontend/components/card/search-result/CardSearchResult.vue b/frontend/components/card/search-result/CardSearchResult.vue index 6b35c6bad..e69de8c74 100644 --- a/frontend/components/card/search-result/CardSearchResult.vue +++ b/frontend/components/card/search-result/CardSearchResult.vue @@ -6,7 +6,7 @@
@@ -111,7 +111,7 @@

@@ -120,7 +120,7 @@ >

@@ -205,8 +205,8 @@

-->
-

@{{ organization.org_name }}

-

@{{ group.group_name }}

+

@{{ organization.orgName }}

+

@{{ group.groupName }}

(() => { if (props.organization) { @@ -297,15 +297,15 @@ const eventType = computed<"action" | "learn">(() => { } }); -const imageURL = computed(() => { - if (props.organization && props.organization.iconURL) { - return props.organization.iconURL; - } else if (props.group && props.group.organization.iconURL) { - return props.group.organization.iconURL; - } else if (props.event && props.event.iconURL) { - return props.event.iconURL; - } else if (props.user && props.user.iconURL) { - return props.user.iconURL; +const imageUrl = computed(() => { + if (props.organization && props.organization.iconUrl) { + return props.organization.iconUrl; + } else if (props.group && props.group.organization.iconUrl) { + return props.group.organization.iconUrl; + } else if (props.event && props.event.iconUrl) { + return props.event.iconUrl; + } else if (props.user && props.user.iconUrl) { + return props.user.iconUrl; } else { return ""; } diff --git a/frontend/components/dropdown/DropdownCreate.vue b/frontend/components/dropdown/DropdownCreate.vue index cbb870dc3..61d45ec41 100644 --- a/frontend/components/dropdown/DropdownCreate.vue +++ b/frontend/components/dropdown/DropdownCreate.vue @@ -23,29 +23,29 @@ const createOptions: MenuSelector[] = [ { id: 1, label: "_global.new_event", - routeURL: "/events/create", - iconURL: `${IconMap.EVENT}`, + routeUrl: "/events/create", + iconUrl: `${IconMap.EVENT}`, selected: false, }, { id: 2, label: "components.dropdown_create.new_organization", - routeURL: "/organizations/create", - iconURL: "IconOrganization", + routeUrl: "/organizations/create", + iconUrl: "IconOrganization", selected: false, }, { id: 3, label: "_global.new_group", - routeURL: "/groups/create", - iconURL: "IconGroup", + routeUrl: "/groups/create", + iconUrl: "IconGroup", selected: false, }, { id: 4, label: "_global.new_resource", - routeURL: "/resources/create", - iconURL: "IconResource", + routeUrl: "/resources/create", + iconUrl: "IconResource", selected: false, }, ]; diff --git a/frontend/components/dropdown/DropdownInfo.vue b/frontend/components/dropdown/DropdownInfo.vue index 965bb5e15..7bd409649 100644 --- a/frontend/components/dropdown/DropdownInfo.vue +++ b/frontend/components/dropdown/DropdownInfo.vue @@ -23,22 +23,22 @@ const infoOptions: MenuSelector[] = [ { id: 1, label: "components.dropdown_info.help", - routeURL: "/help", - iconURL: `${IconMap.CIRCLE_QUESTION}`, + routeUrl: "/help", + iconUrl: `${IconMap.CIRCLE_QUESTION}`, selected: false, }, { id: 2, label: "components._global.documentation", - routeURL: "/docs", - iconURL: `${IconMap.DOCS}`, + routeUrl: "/docs", + iconUrl: `${IconMap.DOCS}`, selected: false, }, { id: 3, label: "components.dropdown_info.legal", - routeURL: "/legal", - iconURL: `${IconMap.LEGAL}`, + routeUrl: "/legal", + iconUrl: `${IconMap.LEGAL}`, selected: false, }, ]; diff --git a/frontend/components/dropdown/DropdownItemsLayout.vue b/frontend/components/dropdown/DropdownItemsLayout.vue index 3bc0ced8f..cb1314835 100644 --- a/frontend/components/dropdown/DropdownItemsLayout.vue +++ b/frontend/components/dropdown/DropdownItemsLayout.vue @@ -8,7 +8,7 @@ @@ -17,7 +17,7 @@ :isSideLeftMenu="isSideLeftMenu" :isButton="false" :label="$t(`${opt.label}`)" - :iconName="opt.iconURL" + :iconName="opt.iconUrl" :active="active" /> diff --git a/frontend/components/dropdown/DropdownUserOptions.vue b/frontend/components/dropdown/DropdownUserOptions.vue index de6766040..ae87380f5 100644 --- a/frontend/components/dropdown/DropdownUserOptions.vue +++ b/frontend/components/dropdown/DropdownUserOptions.vue @@ -32,43 +32,43 @@ const userOptionsSignedIn: MenuSelector[] = [ { id: 1, label: "components.dropdown_user_options.your_profile", - routeURL: "/", - iconURL: `${IconMap.CIRCLE_PERSON}`, + routeUrl: "/", + iconUrl: `${IconMap.CIRCLE_PERSON}`, selected: false, }, { id: 2, label: "components.dropdown_user_options.your_events", - routeURL: "/", - iconURL: `${IconMap.EVENT}`, + routeUrl: "/", + iconUrl: `${IconMap.EVENT}`, selected: false, }, { id: 3, label: "components.dropdown_user_options.your_orgs", - routeURL: "/", - iconURL: `${IconMap.ORGANIZATION}`, + routeUrl: "/", + iconUrl: `${IconMap.ORGANIZATION}`, selected: false, }, { id: 4, label: "_global.notifications", - routeURL: "/", - iconURL: `${IconMap.BELL}`, + routeUrl: "/", + iconUrl: `${IconMap.BELL}`, selected: false, }, { id: 5, label: "_global.settings", - routeURL: "/", - iconURL: `${IconMap.SETTINGS}`, + routeUrl: "/", + iconUrl: `${IconMap.SETTINGS}`, selected: false, }, { id: 6, label: "components.dropdown_user_options.sign_out", - routeURL: "/", - iconURL: `${IconMap.SIGN_OUT}`, + routeUrl: "/", + iconUrl: `${IconMap.SIGN_OUT}`, selected: false, }, ]; @@ -77,15 +77,15 @@ const userOptionsSignedOut: MenuSelector[] = [ { id: 1, label: "_global.sign_up", - routeURL: "/auth/sign-up", - iconURL: `${IconMap.SIGN_IN}`, + routeUrl: "/auth/sign-up", + iconUrl: `${IconMap.SIGN_IN}`, selected: false, }, { id: 1, label: "_global.sign_in", - routeURL: "/auth/sign-in", - iconURL: `${IconMap.CIRCLE_PERSON}`, + routeUrl: "/auth/sign-in", + iconUrl: `${IconMap.CIRCLE_PERSON}`, selected: false, }, ]; diff --git a/frontend/components/feed/Feed.vue b/frontend/components/feed/Feed.vue index 4aceca1a1..58b8454a9 100644 --- a/frontend/components/feed/Feed.vue +++ b/frontend/components/feed/Feed.vue @@ -1,10 +1,10 @@