From da9721bc0d95e4cc4c9883287bbaad22a10bcbfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 09:40:23 +0000 Subject: [PATCH 1/9] Bump braces from 3.0.2 to 3.0.3 in /frontend Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] --- frontend/yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ab43c55b9..cf287a3ec 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3282,11 +3282,11 @@ brace-expansion@^2.0.1: balanced-match "^1.0.0" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browserslist@^4.0.0, browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" @@ -4799,10 +4799,10 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" From e53be980ec64ba050ccf270821c766db5d672968 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 22:22:09 +0000 Subject: [PATCH 2/9] Bump urllib3 from 2.0.7 to 2.2.2 in /backend Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.7 to 2.2.2. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.7...2.2.2) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- backend/requirements-dev.txt | 6 ++---- backend/requirements.txt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 24b25b62f..7bad1ae75 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -28,9 +28,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via pip-tools coverage[toml]==7.4.4 - # via - # coverage - # pytest-cov + # via pytest-cov distlib==0.3.8 # via virtualenv django==4.2.11 @@ -183,7 +181,7 @@ uritemplate==4.1.1 # via # -r requirements.txt # drf-spectacular -urllib3==2.0.7 +urllib3==2.2.2 # via # -r requirements.txt # requests diff --git a/backend/requirements.txt b/backend/requirements.txt index 0a40235fb..13a099bdf 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -92,7 +92,7 @@ typing-extensions==4.8.0 # mypy uritemplate==4.1.1 # via drf-spectacular -urllib3==2.0.7 +urllib3==2.2.2 # via # requests # types-requests From 3212eb2636c36f3fd2d545f29df38059ff25248d Mon Sep 17 00:00:00 2001 From: tosta Date: Mon, 24 Jun 2024 20:15:38 +0200 Subject: [PATCH 3/9] custom bad request error logs setup --- backend/backend/exception_handler.py | 21 +++++++++++++++++++++ backend/backend/settings.py | 1 + 2 files changed, 22 insertions(+) create mode 100644 backend/backend/exception_handler.py diff --git a/backend/backend/exception_handler.py b/backend/backend/exception_handler.py new file mode 100644 index 000000000..aeaca091b --- /dev/null +++ b/backend/backend/exception_handler.py @@ -0,0 +1,21 @@ +import logging +from typing import Any + +from rest_framework.response import Response +from rest_framework.views import exception_handler + +logger = logging.getLogger(__name__) + + +def bad_request_logger(exception: Any, context: dict[str, Any]) -> Response | None: + # Get the DRF exception handler standard error response + response = exception_handler(exception, context) + + if response is not None: + logger.warning( + f"Bad request: {context['request'].path} -" + f"Status Code: {response.status_code} -" + f"Data: {response.data} -" + ) + + return response diff --git a/backend/backend/settings.py b/backend/backend/settings.py index fd7f1c371..1d9f8f501 100644 --- a/backend/backend/settings.py +++ b/backend/backend/settings.py @@ -181,6 +181,7 @@ "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.TokenAuthentication", ], + "EXCEPTION_HANDLER": "backend.exception_handler.bad_request_logger", } SPECTACULAR_SETTINGS = { From 4d7e1254871ba3e6eb03f0ef30a3d4f490da695b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:32:08 +0000 Subject: [PATCH 4/9] Bump djangorestframework from 3.15.1 to 3.15.2 in /backend Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.15.1 to 3.15.2. - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.15.1...3.15.2) --- updated-dependencies: - dependency-name: djangorestframework dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/requirements-dev.txt | 6 ++---- backend/requirements.txt | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt index 24b25b62f..2d67b3569 100644 --- a/backend/requirements-dev.txt +++ b/backend/requirements-dev.txt @@ -28,9 +28,7 @@ charset-normalizer==3.3.2 click==8.1.7 # via pip-tools coverage[toml]==7.4.4 - # via - # coverage - # pytest-cov + # via pytest-cov distlib==0.3.8 # via virtualenv django==4.2.11 @@ -51,7 +49,7 @@ django-stubs-ext==4.2.5 # via # -r requirements.txt # django-stubs -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements.txt # drf-spectacular diff --git a/backend/requirements.txt b/backend/requirements.txt index 0a40235fb..3f35376fe 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -32,7 +32,7 @@ django-stubs-ext==4.2.5 # via # -r requirements.in # django-stubs -djangorestframework==3.15.1 +djangorestframework==3.15.2 # via # -r requirements.in # drf-spectacular From ee98bce3366d0219df8f4166bedb8028faca4b88 Mon Sep 17 00:00:00 2001 From: Andrew Tavis McAllister Date: Sat, 29 Jun 2024 15:43:22 +0200 Subject: [PATCH 5/9] #812 hydration for organization about and index pages --- backend/authentication/admin.py | 22 +- backend/authentication/factories.py | 5 + backend/authentication/models.py | 61 +++-- backend/authentication/serializers.py | 33 ++- backend/authentication/tests.py | 4 +- backend/authentication/urls.py | 8 +- backend/authentication/views.py | 33 ++- backend/content/admin.py | 9 +- backend/content/factories.py | 5 + backend/content/models.py | 81 +++--- backend/content/serializers.py | 117 ++++---- backend/content/urls.py | 13 +- backend/content/views.py | 125 +++++---- backend/entities/admin.py | 13 +- backend/entities/factories.py | 137 ++++++---- backend/entities/models.py | 175 ++++++------ backend/entities/serializers.py | 81 +++--- backend/entities/urls.py | 23 +- backend/entities/views.py | 204 +++++++------- backend/events/admin.py | 9 +- backend/events/factories.py | 19 +- backend/events/models.py | 41 +-- backend/events/serializers.py | 25 +- backend/events/urls.py | 7 + backend/events/views.py | 17 +- backend/fixtures/iso_code_map.json | 9 + backend/fixtures/superuser.json | 3 +- docker-compose.yml | 2 + frontend/components/card/CardAbout.vue | 256 ------------------ frontend/components/card/CardConnect.vue | 41 ++- frontend/components/card/CardDetails.vue | 15 +- frontend/components/card/CardFAQEntry.vue | 6 +- frontend/components/card/CardGetInvolved.vue | 170 ------------ .../card/CardOrgApplicationVote.vue | 2 +- .../{CardTextEntry.vue => CardTextArea.vue} | 0 frontend/components/card/about/CardAbout.vue | 7 + .../components/card/about/CardAboutEvent.vue | 105 +++++++ .../components/card/about/CardAboutGroup.vue | 131 +++++++++ .../card/about/CardAboutOrganization.vue | 130 +++++++++ .../card/discussion/CardDiscussion.vue | 2 +- .../card/discussion/CardDiscussionEntry.vue | 2 +- .../card/discussion/CardDiscussionInput.vue | 2 +- .../card/get-involved/CardGetInvolved.vue | 7 + .../get-involved/CardGetInvolvedEvent.vue | 64 +++++ .../get-involved/CardGetInvolvedGroup.vue | 63 +++++ .../CardGetInvolvedOrganization.vue | 84 ++++++ .../card/search-result/CardSearchResult.vue | 14 +- .../search-result/CardSearchResultEvent.vue | 2 +- .../search-result/CardSearchResultGroup.vue | 2 +- .../CardSearchResultOrganization.vue | 2 +- .../CardSearchResultResource.vue | 2 +- .../search-result/CardSearchResultUser.vue | 2 +- frontend/components/discussion/Discussion.vue | 8 +- .../components/dropdown/DropdownCreate.vue | 2 +- frontend/components/dropdown/DropdownInfo.vue | 2 +- .../dropdown/DropdownItemsLayout.vue | 2 +- .../dropdown/DropdownUserOptions.vue | 2 +- frontend/components/feed/Feed.vue | 34 ++- frontend/components/feed/FeedItem.vue | 2 +- .../components/form/text/FormTextArea.vue | 27 ++ .../FormTextInput.vue} | 0 frontend/components/header/HeaderAppPage.vue | 57 ++-- .../icon/IconOrganizationStatus.vue | 2 +- frontend/components/menu/MenuLinkWrapper.vue | 2 +- frontend/components/menu/MenuSearchResult.vue | 10 +- .../mobile/MenuMobileNavigationDropdown.vue | 2 +- .../meta-tag/MetaTagOrganization.vue | 2 +- .../modal/ModalOrganizationStatus.vue | 4 +- frontend/components/modal/ModalSharePage.vue | 10 +- .../ModalEditFaqEntry.vue} | 3 + .../modal/edit/about/ModalEditAboutEvent.vue | 90 ++++++ .../modal/edit/about/ModalEditAboutGroup.vue | 90 ++++++ .../edit/about/ModalEditAboutOrganization.vue | 99 +++++++ .../components/modal/qr-code/ModalQRCode.vue | 10 +- .../modal/qr-code/ModalQRCodeBtn.vue | 10 +- frontend/components/page/PageBreadcrumbs.vue | 57 +++- frontend/components/page/PageDocs.vue | 5 +- frontend/components/shield/ShieldApp.vue | 2 +- frontend/components/shield/ShieldGitHub.vue | 2 +- .../components/sidebar/right/SidebarRight.vue | 2 +- .../TooltipMenuSearchResultEvent.vue | 2 +- .../TooltipMenuSearchResultGroup.vue | 2 +- .../TooltipMenuSearchResultOrganization.vue | 2 +- .../TooltipMenuSearchResultResource.vue | 2 +- .../TooltipMenuSearchResultUser.vue | 2 +- frontend/composables/put.ts | 20 ++ frontend/composables/useAppPageTexts.ts | 87 ------ frontend/composables/useLinkURL.ts | 10 +- frontend/i18n/en-US.json | 19 +- frontend/pages/auth/reset-password.vue | 2 +- frontend/pages/auth/set-password.vue | 6 +- frontend/pages/auth/sign-in.vue | 6 +- frontend/pages/auth/sign-up.vue | 9 +- frontend/pages/events/[id]/about.vue | 6 +- frontend/pages/events/[id]/index.vue | 2 +- frontend/pages/events/index.vue | 2 +- frontend/pages/groups/create.vue | 5 +- frontend/pages/organizations/[id]/about.vue | 104 +------ .../pages/organizations/[id]/application.vue | 22 ++ .../organizations/[id]/groups/[id]/about.vue | 23 +- frontend/pages/organizations/[id]/index.vue | 2 +- frontend/pages/organizations/create.vue | 37 ++- frontend/pages/organizations/index.vue | 19 +- frontend/stores/auth.ts | 2 - frontend/stores/event.ts | 88 ++++++ frontend/stores/group.ts | 91 +++++++ frontend/stores/organization.ts | 238 ++++++++++++++++ frontend/stores/resource.ts | 0 frontend/types/{ => auth}/user.d.ts | 28 +- frontend/types/content/discussion.d.ts | 34 +++ frontend/types/{ => content}/faq-entry.d.ts | 0 frontend/types/content/resource.d.ts | 33 +++ frontend/types/discussion-entry.d.ts | 8 - frontend/types/discussion-input.d.ts | 8 - frontend/types/discussion.d.ts | 12 - frontend/types/entities/group.d.ts | 102 +++++++ frontend/types/entities/organization.d.ts | 179 ++++++++++++ frontend/types/event.d.ts | 45 --- frontend/types/events/event.d.ts | 120 ++++++++ frontend/types/{ => feed}/feed-item.d.ts | 0 frontend/types/{ => feed}/feed.d.ts | 0 frontend/types/group.d.ts | 36 --- frontend/types/{ => menu}/menu-entry.ts | 0 frontend/types/{ => menu}/menu-selector.d.ts | 0 frontend/types/organization.d.ts | 46 ---- frontend/types/resource.d.ts | 23 -- frontend/utils/testEntities.ts | 18 +- 127 files changed, 2900 insertions(+), 1532 deletions(-) create mode 100644 backend/fixtures/iso_code_map.json delete mode 100644 frontend/components/card/CardAbout.vue delete mode 100644 frontend/components/card/CardGetInvolved.vue rename frontend/components/card/{CardTextEntry.vue => CardTextArea.vue} (100%) create mode 100644 frontend/components/card/about/CardAbout.vue create mode 100644 frontend/components/card/about/CardAboutEvent.vue create mode 100644 frontend/components/card/about/CardAboutGroup.vue create mode 100644 frontend/components/card/about/CardAboutOrganization.vue create mode 100644 frontend/components/card/get-involved/CardGetInvolved.vue create mode 100644 frontend/components/card/get-involved/CardGetInvolvedEvent.vue create mode 100644 frontend/components/card/get-involved/CardGetInvolvedGroup.vue create mode 100644 frontend/components/card/get-involved/CardGetInvolvedOrganization.vue create mode 100644 frontend/components/form/text/FormTextArea.vue rename frontend/components/form/{FormTextField.vue => text/FormTextInput.vue} (100%) rename frontend/components/modal/{ModalEditPageText.vue => edit/ModalEditFaqEntry.vue} (96%) create mode 100644 frontend/components/modal/edit/about/ModalEditAboutEvent.vue create mode 100644 frontend/components/modal/edit/about/ModalEditAboutGroup.vue create mode 100644 frontend/components/modal/edit/about/ModalEditAboutOrganization.vue create mode 100644 frontend/composables/put.ts delete mode 100644 frontend/composables/useAppPageTexts.ts create mode 100644 frontend/pages/organizations/[id]/application.vue create mode 100644 frontend/stores/event.ts create mode 100644 frontend/stores/group.ts create mode 100644 frontend/stores/organization.ts create mode 100644 frontend/stores/resource.ts rename frontend/types/{ => auth}/user.d.ts (59%) create mode 100644 frontend/types/content/discussion.d.ts rename frontend/types/{ => content}/faq-entry.d.ts (100%) create mode 100644 frontend/types/content/resource.d.ts delete mode 100644 frontend/types/discussion-entry.d.ts delete mode 100644 frontend/types/discussion-input.d.ts delete mode 100644 frontend/types/discussion.d.ts create mode 100644 frontend/types/entities/group.d.ts create mode 100644 frontend/types/entities/organization.d.ts delete mode 100644 frontend/types/event.d.ts create mode 100644 frontend/types/events/event.d.ts rename frontend/types/{ => feed}/feed-item.d.ts (100%) rename frontend/types/{ => feed}/feed.d.ts (100%) delete mode 100644 frontend/types/group.d.ts rename frontend/types/{ => menu}/menu-entry.ts (100%) rename frontend/types/{ => menu}/menu-selector.d.ts (100%) delete mode 100644 frontend/types/organization.d.ts delete mode 100644 frontend/types/resource.d.ts diff --git a/backend/authentication/admin.py b/backend/authentication/admin.py index a157b8588..14eb6f68a 100644 --- a/backend/authentication/admin.py +++ b/backend/authentication/admin.py @@ -16,6 +16,21 @@ UserTopic, ) +# MARK: Main Tables + +# Remove default Group. +admin.site.unregister(Group) +admin.site.register(Support) + +# MARK: Bridge Tables + +admin.site.register(UserResource) +admin.site.register(UserTask) +admin.site.register(UserTopic) +admin.site.register(SupportEntityType) + +# MARK: Methods + class UserCreationForm(forms.ModelForm[UserModel]): """ @@ -115,12 +130,5 @@ class UserAdmin(BaseUserAdmin): filter_horizontal = [] -# Remove default Group -admin.site.unregister(Group) -admin.site.register(UserResource) -admin.site.register(UserTask) -admin.site.register(UserTopic) -admin.site.register(SupportEntityType) -admin.site.register(Support) # Now register the new UserAdmin... admin.site.register(UserModel, UserAdmin) diff --git a/backend/authentication/factories.py b/backend/authentication/factories.py index f93309a1d..e5388ce71 100644 --- a/backend/authentication/factories.py +++ b/backend/authentication/factories.py @@ -11,6 +11,8 @@ UserTopic, ) +# MARK: Main Tables + class SupportEntityTypeFactory(factory.django.DjangoModelFactory): class Meta: @@ -61,6 +63,9 @@ def verification_partner( pass +# MARK: Bridge Tables + + class UserResourceFactory(factory.django.DjangoModelFactory): class Meta: model = UserResource diff --git a/backend/authentication/models.py b/backend/authentication/models.py index ed449a604..fac5cf481 100644 --- a/backend/authentication/models.py +++ b/backend/authentication/models.py @@ -15,32 +15,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models - -class SupportEntityType(models.Model): - id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=255) - - def __str__(self) -> str: - return self.name - - -class Support(models.Model): - supporter_type = models.ForeignKey( - "SupportEntityType", on_delete=models.CASCADE, related_name="supporter" - ) - supporter_entity = models.ForeignKey( - "entities.Organization", on_delete=models.CASCADE, related_name="supporter" - ) - supported_type = models.ForeignKey( - "SupportEntityType", on_delete=models.CASCADE, related_name="supported" - ) - supported_entity = models.ForeignKey( - "entities.Organization", on_delete=models.CASCADE, related_name="supported" - ) - creation_date = models.DateTimeField(auto_now_add=True) - - def __str__(self) -> str: - return f"{self.id}" +# MARK: Main Tables class CustomAccountManager(BaseUserManager["UserModel"]): @@ -78,6 +53,33 @@ def create_user( return user +class SupportEntityType(models.Model): + id = models.IntegerField(primary_key=True) + name = models.CharField(max_length=255) + + def __str__(self) -> str: + return self.name + + +class Support(models.Model): + supporter_type = models.ForeignKey( + "SupportEntityType", on_delete=models.CASCADE, related_name="supporter" + ) + supporter_entity = models.ForeignKey( + "entities.Organization", on_delete=models.CASCADE, related_name="supporter" + ) + supported_type = models.ForeignKey( + "SupportEntityType", on_delete=models.CASCADE, related_name="supported" + ) + supported_entity = models.ForeignKey( + "entities.Organization", on_delete=models.CASCADE, related_name="supported" + ) + creation_date = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return f"{self.id}" + + class UserModel(AbstractUser, PermissionsMixin): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) username = models.CharField(max_length=255, unique=True) @@ -92,7 +94,7 @@ class UserModel(AbstractUser, PermissionsMixin): icon_url = models.ForeignKey( "content.Image", on_delete=models.SET_NULL, blank=True, null=True ) - verifictaion_code = models.UUIDField(blank=True, null=True) + verification_code = models.UUIDField(blank=True, null=True) email = models.EmailField(blank=True) is_confirmed = models.BooleanField(default=False) social_links = ArrayField(models.CharField(max_length=255), blank=True, null=True) @@ -104,7 +106,7 @@ class UserModel(AbstractUser, PermissionsMixin): is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) - objects = CustomAccountManager() # type: ignore + objects: CustomAccountManager = CustomAccountManager() # type: ignore USERNAME_FIELD = "username" @@ -112,6 +114,9 @@ def __str__(self) -> str: return self.username +# MARK: Bridge Tables + + class UserResource(models.Model): user_id = models.ForeignKey(UserModel, on_delete=models.CASCADE) resource_id = models.ForeignKey("content.Resource", on_delete=models.CASCADE) diff --git a/backend/authentication/serializers.py b/backend/authentication/serializers.py index 1afc26abc..17e21fc23 100644 --- a/backend/authentication/serializers.py +++ b/backend/authentication/serializers.py @@ -26,18 +26,7 @@ USER = get_user_model() -class SupportEntityTypeSerializer(serializers.ModelSerializer[SupportEntityType]): - class Meta: - model = SupportEntityType - fields = "__all__" - - def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any]]: - if len(data["name"]) < 3: - raise serializers.ValidationError( - _("The field name must be at least 3 characters long."), - code="invalid_name", - ) - return data +# MARK: Main Tables class SupportSerializer(serializers.ModelSerializer[Support]): @@ -57,6 +46,20 @@ def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int return data +class SupportEntityTypeSerializer(serializers.ModelSerializer[SupportEntityType]): + class Meta: + model = SupportEntityType + fields = "__all__" + + def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any]]: + if len(data["name"]) < 3: + raise serializers.ValidationError( + _("The field name must be at least 3 characters long."), + code="invalid_name", + ) + return data + + class UserSerializer(serializers.ModelSerializer[UserModel]): class Meta: model = UserModel @@ -79,6 +82,9 @@ def validate(self, data: Dict[str, Union[str, Any]]) -> Dict[str, Union[str, Any return data +# MARK: Bridge Tables + + class UserResourceSerializer(serializers.ModelSerializer[UserResource]): class Meta: model = UserResource @@ -97,6 +103,9 @@ class Meta: fields = "__all__" +# MARK: Methods + + class SignupSerializer(serializers.ModelSerializer[UserModel]): password_confirmed = serializers.CharField(write_only=True) diff --git a/backend/authentication/tests.py b/backend/authentication/tests.py index 4d957ed40..44ae41781 100644 --- a/backend/authentication/tests.py +++ b/backend/authentication/tests.py @@ -112,7 +112,7 @@ def test_signup(client: Client) -> None: assert response.status_code == 201 assert UserModel.objects.filter(username=username) # code for Email confirmation is generated and is a UUID - assert isinstance(user.verifictaion_code, UUID) + assert isinstance(user.verification_code, UUID) assert user.is_confirmed is False # Confirmation Email was sent assert len(mail.outbox) == 1 @@ -148,7 +148,7 @@ def test_signup(client: Client) -> None: assert UserModel.objects.filter(username=second_username).exists() assert user.email == "" assert user.is_confirmed is False - assert user.verifictaion_code is None + assert user.verification_code is None @pytest.mark.django_db diff --git a/backend/authentication/urls.py b/backend/authentication/urls.py index 6507e3c8e..d5699749b 100644 --- a/backend/authentication/urls.py +++ b/backend/authentication/urls.py @@ -7,14 +7,18 @@ router = DefaultRouter() -router.register(r"support_entity_types", views.SupportEntityTypeViewSet) +# MARK: Main Tables + router.register(r"supports", views.SupportViewSet) router.register(r"users", views.UserViewSet) + +# MARK: Bridge Tables + +router.register(r"support_entity_types", views.SupportEntityTypeViewSet) router.register(r"user_resources", views.UserResourceViewSet) router.register(r"user_tasks", views.UserTaskViewSet) router.register(r"user_topics", views.UserTopicViewSet) - urlpatterns = [ path("", include(router.urls)), path("signup/", views.SignupView.as_view(), name="signup"), diff --git a/backend/authentication/views.py b/backend/authentication/views.py index caacec8e9..cfe8d9424 100644 --- a/backend/authentication/views.py +++ b/backend/authentication/views.py @@ -43,10 +43,7 @@ ACTIVIST_EMAIL = os.getenv("ACTIVIST_EMAIL") -class SupportEntityTypeViewSet(viewsets.ModelViewSet[SupportEntityType]): - queryset = SupportEntityType.objects.all() - pagination_class = CustomPagination - serializer_class = SupportEntityTypeSerializer +# MARK: Main Tables class SupportViewSet(viewsets.ModelViewSet[Support]): @@ -61,6 +58,15 @@ class UserViewSet(viewsets.ModelViewSet[UserModel]): serializer_class = UserSerializer +# MARK: Bridge Tables + + +class SupportEntityTypeViewSet(viewsets.ModelViewSet[SupportEntityType]): + queryset = SupportEntityType.objects.all() + pagination_class = CustomPagination + serializer_class = SupportEntityTypeSerializer + + class UserResourceViewSet(viewsets.ModelViewSet[UserResource]): queryset = UserResource.objects.all() pagination_class = CustomPagination @@ -79,6 +85,9 @@ class UserTopicViewSet(viewsets.ModelViewSet[UserTopic]): serializer_class = UserTopicSerializer +# MARK: Methods + + class SignupView(APIView): queryset = UserModel.objects.all() permission_classes = (AllowAny,) @@ -91,9 +100,9 @@ def post(self, request: Request) -> Response: user: UserModel = serializer.save() if user.email != "": - user.verifictaion_code = uuid.uuid4() + user.verification_code = uuid.uuid4() - confirmation_link = f"{FRONTEND_BASE_URL}/confirm/{user.verifictaion_code}" + confirmation_link = f"{FRONTEND_BASE_URL}/confirm/{user.verification_code}" message = f"Welcome to activist.org, {user.username}!, Please confirm your email address by clicking the link: {confirmation_link}" html_message = render_to_string( template_name="signup_email.html", @@ -120,12 +129,12 @@ def post(self, request: Request) -> Response: ) @extend_schema( - parameters=[OpenApiParameter(name="verifictaion_code", type=str, required=True)] + parameters=[OpenApiParameter(name="verification_code", type=str, required=True)] ) def get(self, request: Request) -> Response: """Confirm a user's email address.""" - verifictaion_code = request.GET.get("verifictaion_code") - user = UserModel.objects.filter(verifictaion_code=verifictaion_code).first() + verification_code = request.GET.get("verification_code") + user = UserModel.objects.filter(verification_code=verification_code).first() if user is None: return Response( @@ -134,7 +143,7 @@ def get(self, request: Request) -> Response: ) user.is_confirmed = True - user.verifictaion_code = "" + user.verification_code = "" user.save() return Response( @@ -183,9 +192,9 @@ def get(self, request: Request) -> Response: status=status.HTTP_404_NOT_FOUND, ) - user.verifictaion_code = uuid.uuid4() + user.verification_code = uuid.uuid4() - pwreset_link = f"{FRONTEND_BASE_URL}/pwreset/{user.verifictaion_code}" + pwreset_link = f"{FRONTEND_BASE_URL}/pwreset/{user.verification_code}" message = "Reset your password at activist.org" html_message = render_to_string( template_name="pwreset_email.html", diff --git a/backend/content/admin.py b/backend/content/admin.py index 4a18334df..b1d867411 100644 --- a/backend/content/admin.py +++ b/backend/content/admin.py @@ -11,11 +11,16 @@ TopicFormat, ) +# MARK: Main Tables + admin.site.register(Faq) admin.site.register(Image) +admin.site.register(IsoCodeMap) admin.site.register(Resource) -admin.site.register(ResourceTopic) admin.site.register(Task) admin.site.register(Topic) + +# MARK: Main Tables + +admin.site.register(ResourceTopic) admin.site.register(TopicFormat) -admin.site.register(IsoCodeMap) diff --git a/backend/content/factories.py b/backend/content/factories.py index 3f33f05de..7b6885af4 100644 --- a/backend/content/factories.py +++ b/backend/content/factories.py @@ -4,6 +4,8 @@ from .models import Faq, Resource, ResourceTopic, Task, Topic, TopicFormat +# MARK: Main Tables + class FaqFactory(factory.django.DjangoModelFactory): class Meta: @@ -48,6 +50,9 @@ class Meta: deprecation_date = factory.Faker("date") +# MARK: Bridge Tables + + class ResourceTopicFactory(factory.django.DjangoModelFactory): class Meta: model = ResourceTopic diff --git a/backend/content/models.py b/backend/content/models.py index 55c7b4885..dd9452701 100644 --- a/backend/content/models.py +++ b/backend/content/models.py @@ -10,6 +10,8 @@ from backend.mixins.models import CreationDeletionMixin +# MARK: Main Tables + class Discussion(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) @@ -25,18 +27,6 @@ def __str__(self) -> str: return f"{self.id}" -class DiscussionEntry(models.Model): - id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - discussion_id = models.ForeignKey(Discussion, on_delete=models.CASCADE) - created_by = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) - text = models.CharField(max_length=255, blank=True) - creation_date = models.DateTimeField(auto_now_add=True) - deletion_date = models.DateTimeField(null=True, blank=True) - - def __str__(self) -> str: - return f"{self.id}" - - class Faq(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) org_id = models.ForeignKey("entities.Organization", on_delete=models.CASCADE) @@ -48,6 +38,21 @@ def __str__(self) -> str: return self.question +class Image(models.Model): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + image_location = models.ImageField( + upload_to="images/", validators=[validate_image_file_extension] + ) + creation_date = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return f"{self.id}" + + +class IsoCodeMap(models.Model): + code = models.CharField(max_length=2) + + class Resource(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) name = models.CharField(max_length=255) @@ -64,6 +69,15 @@ def __str__(self) -> str: return self.name +class Tag(models.Model): + id = models.IntegerField(primary_key=True) + text = models.CharField(max_length=255) + creation_date = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return f"{self.id}" + + class Task(CreationDeletionMixin): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) name = models.CharField(max_length=255) @@ -87,18 +101,24 @@ def __str__(self) -> str: return self.name -class Tag(models.Model): - id = models.IntegerField(primary_key=True) - text = models.CharField(max_length=255) +# MARK: Bridge Tables + + +class DiscussionEntry(models.Model): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + discussion_id = models.ForeignKey(Discussion, on_delete=models.CASCADE) + created_by = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) + text = models.CharField(max_length=255, blank=True) creation_date = models.DateTimeField(auto_now_add=True) + deletion_date = models.DateTimeField(null=True, blank=True) def __str__(self) -> str: return f"{self.id}" -class ResourceTopic(models.Model): - resource_id = models.ForeignKey(Resource, on_delete=models.CASCADE) - topic_id = models.ForeignKey(Topic, on_delete=models.CASCADE) +class DiscussionTag(models.Model): + discussion_id = models.ForeignKey(Discussion, on_delete=models.CASCADE) + tag_id = models.ForeignKey(Tag, on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" @@ -112,32 +132,17 @@ def __str__(self) -> str: return f"{self.id}" -class TopicFormat(models.Model): +class ResourceTopic(models.Model): + resource_id = models.ForeignKey(Resource, on_delete=models.CASCADE) topic_id = models.ForeignKey(Topic, on_delete=models.CASCADE) - format_id = models.ForeignKey("events.Format", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" -class DiscussionTag(models.Model): - discussion_id = models.ForeignKey(Discussion, on_delete=models.CASCADE) - tag_id = models.ForeignKey(Tag, on_delete=models.CASCADE) - - def __str__(self) -> str: - return f"{self.id}" - - -class Image(models.Model): - id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - image_location = models.ImageField( - upload_to="images/", validators=[validate_image_file_extension] - ) - creation_date = models.DateTimeField(auto_now_add=True) +class TopicFormat(models.Model): + topic_id = models.ForeignKey(Topic, on_delete=models.CASCADE) + format_id = models.ForeignKey("events.Format", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" - - -class IsoCodeMap(models.Model): - code = models.CharField(max_length=2) diff --git a/backend/content/serializers.py b/backend/content/serializers.py index c3a0aa20b..8c972ee19 100644 --- a/backend/content/serializers.py +++ b/backend/content/serializers.py @@ -29,6 +29,8 @@ TopicFormat, ) +# MARK: Main Tables + class DiscussionSerializer(serializers.ModelSerializer[Discussion]): class Meta: @@ -44,25 +46,46 @@ class Meta: ] -class DiscussionEntrySerializer(serializers.ModelSerializer[DiscussionEntry]): - class Meta: - model = DiscussionEntry - fields = [ - "id", - "discussion_id", - "created_by", - "text", - "creation_date", - "deletion_date", - ] - - class FaqSerializer(serializers.ModelSerializer[Faq]): class Meta: model = Faq fields = ["id", "question", "org_id", "answer", "last_updated"] +class ImageSerializer(serializers.ModelSerializer[Image]): + class Meta: + model = Image + fields = ["id", "image_location", "creation_date"] + read_only_fields = ["id", "creation_date"] + + def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int]]: + image_extensions = [".jpg", ".jpeg", ".png"] + img_format = "" + + try: + with PilImage.open(data["image_location"]) as img: + img.verify() + img_format = img.format.lower() + except Exception: + raise serializers.ValidationError( + _("The image is not valid."), code="corrupted_file" + ) + + if img_format not in image_extensions: + raise serializers.ValidationError( + _("The image must be in jpg, jpeg or png format."), + code="invalid_extension", + ) + + return data + + +class IsoCodeMapSerializer(serializers.ModelSerializer[IsoCodeMap]): + class Meta: + model = IsoCodeMap + fields = "__all__" + + class ResourceSerializer(serializers.ModelSerializer[Resource]): class Meta: model = Resource @@ -78,6 +101,12 @@ class Meta: ] +class TagSerializer(serializers.ModelSerializer[Tag]): + class Meta: + model = Tag + fields = "__all__" + + class TaskSerializer(serializers.ModelSerializer[Task]): class Meta: model = Task @@ -112,65 +141,41 @@ def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int return data -class TagSerializer(serializers.ModelSerializer[Tag]): - class Meta: - model = Tag - fields = "__all__" +# MARK: Bridge Tables -class ResourceTopicSerializer(serializers.ModelSerializer[ResourceTopic]): +class DiscussionEntrySerializer(serializers.ModelSerializer[DiscussionEntry]): class Meta: - model = ResourceTopic - fields = "__all__" + model = DiscussionEntry + fields = [ + "id", + "discussion_id", + "created_by", + "text", + "creation_date", + "deletion_date", + ] -class ResourceTagSerializer(serializers.ModelSerializer[ResourceTag]): +class DiscussionTagSerializer(serializers.ModelSerializer[DiscussionTag]): class Meta: - model = ResourceTag + model = DiscussionTag fields = "__all__" -class TopicFormatSerializer(serializers.ModelSerializer[TopicFormat]): +class ResourceTagSerializer(serializers.ModelSerializer[ResourceTag]): class Meta: - model = TopicFormat + model = ResourceTag fields = "__all__" -class DiscussionTagSerializer(serializers.ModelSerializer[DiscussionTag]): +class ResourceTopicSerializer(serializers.ModelSerializer[ResourceTopic]): class Meta: - model = DiscussionTag + model = ResourceTopic fields = "__all__" -class ImageSerializer(serializers.ModelSerializer[Image]): - class Meta: - model = Image - fields = ["id", "image_location", "creation_date"] - read_only_fields = ["id", "creation_date"] - - def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int]]: - image_extensions = [".jpg", ".jpeg", ".png"] - img_format = "" - - try: - with PilImage.open(data["image_location"]) as img: - img.verify() - img_format = img.format.lower() - except Exception: - raise serializers.ValidationError( - _("The image is not valid."), code="corrupted_file" - ) - - if img_format not in image_extensions: - raise serializers.ValidationError( - _("The image must be in jpg, jpeg or png format."), - code="invalid_extension", - ) - - return data - - -class IsoCodeMapSerializer(serializers.ModelSerializer[IsoCodeMap]): +class TopicFormatSerializer(serializers.ModelSerializer[TopicFormat]): class Meta: - model = IsoCodeMap + model = TopicFormat fields = "__all__" diff --git a/backend/content/urls.py b/backend/content/urls.py index be9aeca5b..a05c67c7e 100644 --- a/backend/content/urls.py +++ b/backend/content/urls.py @@ -6,18 +6,23 @@ app_name = "content" router = DefaultRouter() + +# MARK: Main Tables + router.register(r"discussion", views.DiscussionViewSet) -router.register(r"discussion_entry", views.DiscussionEntryViewSet) +router.register(r"faq", views.FaqViewSet) router.register(r"images", views.ImageViewSet) router.register(r"resources", views.ResourceViewSet) router.register(r"tasks", views.TaskViewSet) router.register(r"topics", views.TopicViewSet) + +# MARK: Bridge Tables + +router.register(r"discussion_entry", views.DiscussionEntryViewSet) router.register(r"resource_topics", views.ResourceTopicViewSet) router.register(r"topic_formats", views.TopicFormatViewSet) -router.register(r"faq", views.FaqViewSet) - urlpatterns = [ path("", include(router.urls)), - path("iso_codes/", views.IsoCodeMapListAPIView.as_view(), name="iso_codes"), + path("iso_code_map/", views.IsoCodeMapListAPIView.as_view(), name="iso_code_map"), ] diff --git a/backend/content/views.py b/backend/content/views.py index 2ece0d467..bab12a56b 100644 --- a/backend/content/views.py +++ b/backend/content/views.py @@ -34,6 +34,8 @@ TopicSerializer, ) +# MARK: Main Tables + class DiscussionViewSet(viewsets.ModelViewSet[Discussion]): queryset = Discussion.objects.all() @@ -115,32 +117,57 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) -class DiscussionEntryViewSet(viewsets.ModelViewSet[DiscussionEntry]): - queryset = DiscussionEntry.objects.all() - serializer_class = DiscussionEntrySerializer +class FaqViewSet(viewsets.ModelViewSet[Faq]): + queryset = Faq.objects.all() + serializer_class = FaqSerializer + pagination_class = CustomPagination + + +class ImageViewSet(viewsets.ModelViewSet[Image]): + queryset = Image.objects.all() + serializer_class = ImageSerializer + pagination_class = CustomPagination + + +class IsoCodeMapListAPIView(ListAPIView[IsoCodeMap]): + queryset = IsoCodeMap.objects.all() + serializer_class = IsoCodeMapSerializer + + def get(self, request: Request) -> Response: + queryset = self.get_queryset() + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + + +class ResourceViewSet(viewsets.ModelViewSet[Resource]): + queryset = Resource.objects.all() + serializer_class = ResourceSerializer pagination_class = CustomPagination throttle_classes = [AnonRateThrottle, UserRateThrottle] permission_classes = [IsAuthenticatedOrReadOnly] def create(self, request: Request) -> Response: if request.user.is_authenticated: - request.data["created_by"] = request.user serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save() + serializer.save(created_by=request.user) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response( - {"error": "You are not allowed to create a discussion entry."}, + {"error": "You are not allowed to create a resource."}, status=status.HTTP_403_FORBIDDEN, ) def retrieve(self, request: Request, pk: str | None = None) -> Response: - queryset = self.get_queryset() - item = queryset.filter(id=pk).first() + if request.user.is_authenticated: + query = self.queryset.filter( + Q(is_private=False) | Q(is_private=True, created_by=request.user), id=pk + ) + else: + query = self.queryset.filter(Q(is_private=False), id=pk) - serializer = self.get_serializer(item) - return Response(serializer.data, status=status.HTTP_200_OK) + serializer = self.get_serializer(query) + return Response(serializer.data) def list(self, request: Request) -> Response: if request.user.is_authenticated: @@ -148,7 +175,7 @@ def list(self, request: Request) -> Response: Q(is_private=False) | Q(is_private=True, created_by=request.user) ) else: - query = self.queryset.filter() + query = self.queryset.filter(is_private=False) serializer = self.get_serializer(query, many=True) return self.get_paginated_response(self.paginate_queryset(serializer.data)) @@ -157,7 +184,7 @@ def update(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to update this discussion entry."}, + {"error": "You are not allowed to update this resource."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(item, data=request.data) @@ -169,7 +196,7 @@ def partial_update(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to update this discussion entry."}, + {"error": "You are not allowed to update this resource."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(item, data=request.data, partial=True) @@ -181,7 +208,7 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to delete this discussion entry."}, + {"error": "You are not allowed to delete this resource."}, status=status.HTTP_403_FORBIDDEN, ) @@ -189,47 +216,48 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) -class FaqViewSet(viewsets.ModelViewSet[Faq]): - queryset = Faq.objects.all() - serializer_class = FaqSerializer +class TaskViewSet(viewsets.ModelViewSet[Task]): + queryset = Task.objects.all() + serializer_class = TaskSerializer pagination_class = CustomPagination -class ImageViewSet(viewsets.ModelViewSet[Image]): - queryset = Image.objects.all() - serializer_class = ImageSerializer +class TopicViewSet(viewsets.ModelViewSet[Topic]): + queryset = Topic.objects.all() + serializer_class = TopicSerializer + pagination_class = CustomPagination -class ResourceViewSet(viewsets.ModelViewSet[Resource]): - queryset = Resource.objects.all() - serializer_class = ResourceSerializer +# MARK: Bridge Tables + + +class DiscussionEntryViewSet(viewsets.ModelViewSet[DiscussionEntry]): + queryset = DiscussionEntry.objects.all() + serializer_class = DiscussionEntrySerializer pagination_class = CustomPagination throttle_classes = [AnonRateThrottle, UserRateThrottle] permission_classes = [IsAuthenticatedOrReadOnly] def create(self, request: Request) -> Response: if request.user.is_authenticated: + request.data["created_by"] = request.user serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - serializer.save(created_by=request.user) + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response( - {"error": "You are not allowed to create a resource."}, + {"error": "You are not allowed to create a discussion entry."}, status=status.HTTP_403_FORBIDDEN, ) def retrieve(self, request: Request, pk: str | None = None) -> Response: - if request.user.is_authenticated: - query = self.queryset.filter( - Q(is_private=False) | Q(is_private=True, created_by=request.user), id=pk - ) - else: - query = self.queryset.filter(Q(is_private=False), id=pk) + queryset = self.get_queryset() + item = queryset.filter(id=pk).first() - serializer = self.get_serializer(query) - return Response(serializer.data) + serializer = self.get_serializer(item) + return Response(serializer.data, status=status.HTTP_200_OK) def list(self, request: Request) -> Response: if request.user.is_authenticated: @@ -237,7 +265,7 @@ def list(self, request: Request) -> Response: Q(is_private=False) | Q(is_private=True, created_by=request.user) ) else: - query = self.queryset.filter(is_private=False) + query = self.queryset.filter() serializer = self.get_serializer(query, many=True) return self.get_paginated_response(self.paginate_queryset(serializer.data)) @@ -246,7 +274,7 @@ def update(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to update this resource."}, + {"error": "You are not allowed to update this discussion entry."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(item, data=request.data) @@ -258,7 +286,7 @@ def partial_update(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to update this resource."}, + {"error": "You are not allowed to update this discussion entry."}, status=status.HTTP_403_FORBIDDEN, ) serializer = self.get_serializer(item, data=request.data, partial=True) @@ -270,7 +298,7 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: item = self.get_object() if item.created_by != request.user: return Response( - {"error": "You are not allowed to delete this resource."}, + {"error": "You are not allowed to delete this discussion entry."}, status=status.HTTP_403_FORBIDDEN, ) @@ -278,19 +306,6 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: return Response(status=status.HTTP_204_NO_CONTENT) -class TaskViewSet(viewsets.ModelViewSet[Task]): - queryset = Task.objects.all() - serializer_class = TaskSerializer - pagination_class = CustomPagination - - -class TopicViewSet(viewsets.ModelViewSet[Topic]): - queryset = Topic.objects.all() - serializer_class = TopicSerializer - - pagination_class = CustomPagination - - class ResourceTopicViewSet(viewsets.ModelViewSet[ResourceTopic]): queryset = ResourceTopic.objects.all() serializer_class = ResourceTopicSerializer @@ -301,13 +316,3 @@ class TopicFormatViewSet(viewsets.ModelViewSet[TopicFormat]): queryset = TopicFormat.objects.all() serializer_class = TopicFormatSerializer pagination_class = CustomPagination - - -class IsoCodeMapListAPIView(ListAPIView[IsoCodeMap]): - queryset = IsoCodeMap.objects.all() - serializer_class = IsoCodeMapSerializer - - def get(self, request: Request) -> Response: - queryset = self.get_queryset() - serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/backend/entities/admin.py b/backend/entities/admin.py index 47e4003e5..9bec21206 100644 --- a/backend/entities/admin.py +++ b/backend/entities/admin.py @@ -6,6 +6,7 @@ GroupImage, GroupMember, GroupResource, + GroupText, GroupTopic, Organization, OrganizationApplication, @@ -14,22 +15,32 @@ OrganizationMember, OrganizationResource, OrganizationTask, + OrganizationText, OrganizationTopic, StatusType, ) +# MARK: Main Tables + admin.site.register(Group) +admin.site.register(Organization) + +# MARK: Bridge Tables + admin.site.register(GroupEvent) admin.site.register(GroupImage) admin.site.register(GroupMember) admin.site.register(GroupResource) +admin.site.register(GroupText) admin.site.register(GroupTopic) -admin.site.register(Organization) + admin.site.register(OrganizationApplication) admin.site.register(OrganizationEvent) admin.site.register(OrganizationImage) admin.site.register(OrganizationMember) admin.site.register(OrganizationResource) admin.site.register(OrganizationTask) +admin.site.register(OrganizationText) admin.site.register(OrganizationTopic) + admin.site.register(StatusType) diff --git a/backend/entities/factories.py b/backend/entities/factories.py index 56f5ef441..b7bb3692f 100644 --- a/backend/entities/factories.py +++ b/backend/entities/factories.py @@ -8,6 +8,7 @@ GroupImage, GroupMember, GroupResource, + GroupText, GroupTopic, Organization, OrganizationApplication, @@ -17,9 +18,12 @@ OrganizationMember, OrganizationResource, OrganizationTask, + OrganizationText, OrganizationTopic, ) +# MARK: Main Tables + class OrganizationFactory(factory.django.DjangoModelFactory): class Meta: @@ -33,6 +37,76 @@ class Meta: is_high_risk = factory.Faker("boolean") +class GroupFactory(factory.django.DjangoModelFactory): + class Meta: + model = Group + + org_id = factory.SubFactory(OrganizationFactory) + name = factory.Faker("word") + tagline = factory.Faker("word") + description = factory.Faker("text") + social_links = factory.List([factory.Faker("word") for _ in range(10)]) + created_by = factory.SubFactory("authentication.factories.UserFactory") + creation_date = factory.LazyFunction(datetime.datetime.now) + deletion_date = factory.LazyFunction(datetime.datetime.now) + + +# MARK: Bridge Tables + + +class GroupEventFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupEvent + + group_id = factory.SubFactory(GroupFactory) + event_id = factory.SubFactory("events.factories.EventFactory") + + +class GroupImageFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupImage + + group_id = factory.SubFactory(GroupFactory) + image_id = factory.SubFactory("content.factories.ImageFactory") + + +class GroupMemberFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupMember + + group_id = factory.SubFactory(GroupFactory) + user_id = factory.SubFactory("authentication.factories.UserFactory") + is_admin = factory.Faker("boolean") + + +class GroupResourceFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupResource + + group_id = factory.SubFactory(GroupFactory) + resource_id = factory.SubFactory("content.factories.ResourceFactory") + + +class GroupTextFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupText + + group_id = factory.SubFactory(GroupFactory) + iso = factory.Faker("word") + primary = factory.Fakeer("boolean") + description = factory.Faker("text") + get_involved = factory.Faker("text") + donate_prompt = factory.Faker("text") + + +class GroupTopicFactory(factory.django.DjangoModelFactory): + class Meta: + model = GroupTopic + + group_id = factory.SubFactory(GroupFactory) + topic_id = factory.SubFactory("content.factories.TopicFactory") + + class OrganizationApplicationStatusFactory(factory.django.DjangoModelFactory): class Meta: model = OrganizationApplicationStatus @@ -87,20 +161,6 @@ class Meta: resource_id = factory.SubFactory("content.factories.ResourceFactory") -class GroupFactory(factory.django.DjangoModelFactory): - class Meta: - model = Group - - org_id = factory.SubFactory(OrganizationFactory) - name = factory.Faker("word") - tagline = factory.Faker("word") - description = factory.Faker("text") - social_links = factory.List([factory.Faker("word") for _ in range(10)]) - created_by = factory.SubFactory("authentication.factories.UserFactory") - creation_date = factory.LazyFunction(datetime.datetime.now) - deletion_date = factory.LazyFunction(datetime.datetime.now) - - class OrganizationTaskFactory(factory.django.DjangoModelFactory): class Meta: model = OrganizationTask @@ -110,50 +170,21 @@ class Meta: group_id = factory.SubFactory(GroupFactory) -class OrganizationTopicFactory(factory.django.DjangoModelFactory): +class OrganizationTextFactory(factory.django.DjangoModelFactory): class Meta: - model = OrganizationTopic + model = OrganizationText org_id = factory.SubFactory(OrganizationFactory) - topic_id = factory.SubFactory("content.factories.TopicFactory") - - -class GroupEventFactory(factory.django.DjangoModelFactory): - class Meta: - model = GroupEvent - - group_id = factory.SubFactory(GroupFactory) - event_id = factory.SubFactory("events.factories.EventFactory") - - -class GroupImageFactory(factory.django.DjangoModelFactory): - class Meta: - model = GroupImage - - group_id = factory.SubFactory(GroupFactory) - image_id = factory.SubFactory("content.factories.ImageFactory") - - -class GroupMemberFactory(factory.django.DjangoModelFactory): - class Meta: - model = GroupMember - - group_id = factory.SubFactory(GroupFactory) - user_id = factory.SubFactory("authentication.factories.UserFactory") - is_admin = factory.Faker("boolean") - - -class GroupResourceFactory(factory.django.DjangoModelFactory): - class Meta: - model = GroupResource - - group_id = factory.SubFactory(GroupFactory) - resource_id = factory.SubFactory("content.factories.ResourceFactory") + iso = factory.Faker("word") + primary = factory.Fakeer("boolean") + description = factory.Faker("text") + get_involved = factory.Faker("text") + donate_prompt = factory.Faker("text") -class GroupTopicFactory(factory.django.DjangoModelFactory): +class OrganizationTopicFactory(factory.django.DjangoModelFactory): class Meta: - model = GroupTopic + model = OrganizationTopic - group_id = factory.SubFactory(GroupFactory) + org_id = factory.SubFactory(OrganizationFactory) topic_id = factory.SubFactory("content.factories.TopicFactory") diff --git a/backend/entities/models.py b/backend/entities/models.py index 1ba8f41e1..5f72bc985 100644 --- a/backend/entities/models.py +++ b/backend/entities/models.py @@ -7,22 +7,27 @@ from django.contrib.postgres.fields import ArrayField from django.db import models -from authentication.enums import StatusTypes +from authentication import enums + +# MARK: Main Tables class Organization(models.Model): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - name = models.CharField(max_length=255, unique=True) + name = models.CharField(max_length=255) tagline = models.CharField(max_length=255, blank=True) icon_url = models.OneToOneField( "content.Image", on_delete=models.CASCADE, null=True, blank=True ) + location = models.CharField(max_length=255) + # location_id = models.OneToOneField( + # "content.Location", on_delete=models.CASCADE, null=False, blank=False + # ) created_by = models.ForeignKey( "authentication.UserModel", related_name="created_orgs", on_delete=models.CASCADE, ) - description = models.TextField(max_length=500) social_links = ArrayField( models.CharField(max_length=255), default=list, blank=True ) @@ -31,7 +36,7 @@ class Organization(models.Model): status = models.ForeignKey( "StatusType", on_delete=models.CASCADE, - default=StatusTypes.PENDING, + default=enums.StatusTypes.PENDING.value, blank=True, null=True, ) @@ -43,39 +48,54 @@ def __str__(self) -> str: return self.name -class OrganizationApplicationStatus(models.Model): - id = models.IntegerField(primary_key=True) - status_name = models.CharField(max_length=255) +class Group(models.Model): + id = models.UUIDField(primary_key=True, default=uuid4, editable=False) + name = models.CharField(max_length=255) + tagline = models.CharField(max_length=255, blank=True) + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) + location = models.CharField(max_length=255) + # location_id = models.OneToOneField( + # "content.Location", on_delete=models.CASCADE, null=False, blank=False + # ) + about_images = models.ManyToManyField( + "content.Image", related_name="about_img", blank=True + ) + created_by = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) + get_involved_url = models.URLField(blank=True) + social_links = ArrayField( + models.CharField(max_length=255), default=list, blank=True + ) + category = models.CharField(max_length=255) + creation_date = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: - return self.status_name + return self.name -class OrganizationApplication(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) - status = models.ForeignKey("StatusType", on_delete=models.CASCADE, default=1) - orgs_in_favor = models.ManyToManyField( - "entities.Organization", related_name="in_favor", blank=True - ) - orgs_against = models.ManyToManyField( - "entities.Organization", related_name="against", blank=True +class Status(models.Model): + status_type = models.ForeignKey("StatusType", on_delete=models.CASCADE) + org_id = models.ForeignKey( + Organization, on_delete=models.CASCADE, related_name="org_status" ) - creation_date = models.DateTimeField(auto_now_add=True) + user_id = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) def __str__(self) -> str: - return f"{self.creation_date}" + return f"{self.org_id.name} - {self.status_type}" -class OrganizationEvent(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) +# MARK: Bridge Tables + + +class GroupEvent(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) event_id = models.ForeignKey("events.Event", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" -class OrganizationImage(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) +class GroupImage(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) image_id = models.ForeignKey("content.Image", on_delete=models.CASCADE) sequence_index = models.IntegerField() @@ -83,8 +103,8 @@ def __str__(self) -> str: return f"{self.id}" -class OrganizationMember(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) +class GroupMember(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) user_id = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) is_owner = models.BooleanField(default=False) is_admin = models.BooleanField(default=False) @@ -94,73 +114,64 @@ def __str__(self) -> str: return f"{self.id}" -class OrganizationResource(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) +class GroupResource(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) resource_id = models.ForeignKey("content.Resource", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" -class Group(models.Model): - id = models.UUIDField(primary_key=True, default=uuid4, editable=False) - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) - name = models.CharField(max_length=255) - tagline = models.CharField(max_length=255, blank=True) - about_images = models.ManyToManyField( - "content.Image", related_name="about_img", blank=True - ) - created_by = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) - get_involved_url = models.URLField(blank=True) +class GroupText(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) + iso = models.ForeignKey("content.IsoCodeMap", on_delete=models.CASCADE) + primary = models.BooleanField(default=False) description = models.TextField(max_length=500) - social_links = ArrayField( - models.CharField(max_length=255), default=list, blank=True - ) - category = models.CharField(max_length=255) - creation_date = models.DateTimeField(auto_now_add=True) + get_involved = models.TextField(max_length=500, blank=True) + donate_prompt = models.TextField(max_length=500, blank=True) + + +class GroupTopic(models.Model): + group_id = models.ForeignKey(Group, on_delete=models.CASCADE) + topic_id = models.ForeignKey("content.Topic", on_delete=models.CASCADE) def __str__(self) -> str: - return self.name + return f"{self.id}" -class OrganizationTask(models.Model): +class OrganizationApplication(models.Model): org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) - task_id = models.ForeignKey("content.Task", on_delete=models.CASCADE) - group_id = models.ForeignKey( - "Group", on_delete=models.CASCADE, null=True, blank=True + status = models.ForeignKey("StatusType", on_delete=models.CASCADE, default=1) + orgs_in_favor = models.ManyToManyField( + "entities.Organization", related_name="in_favor", blank=True ) + orgs_against = models.ManyToManyField( + "entities.Organization", related_name="against", blank=True + ) + creation_date = models.DateTimeField(auto_now_add=True) def __str__(self) -> str: - return f"{self.id}" + return f"{self.creation_date}" -class OrganizationTopic(models.Model): - org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) - topic_id = models.ForeignKey("content.Topic", on_delete=models.CASCADE) +class OrganizationApplicationStatus(models.Model): + id = models.IntegerField(primary_key=True) + status_name = models.CharField(max_length=255) def __str__(self) -> str: - return f"{self.id}" + return self.status_name -class OrganizationText(models.Model): +class OrganizationEvent(models.Model): org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) - iso = models.ForeignKey("content.IsoCodeMap", on_delete=models.CASCADE) - primary = models.BooleanField(default=False) - description = models.TextField(max_length=500) - get_involved = models.TextField(max_length=500) - donate_prompt = models.TextField(max_length=500) - - -class GroupEvent(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) event_id = models.ForeignKey("events.Event", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" -class GroupImage(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) +class OrganizationImage(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) image_id = models.ForeignKey("content.Image", on_delete=models.CASCADE) sequence_index = models.IntegerField() @@ -168,8 +179,8 @@ def __str__(self) -> str: return f"{self.id}" -class GroupMember(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) +class OrganizationMember(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) user_id = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) is_owner = models.BooleanField(default=False) is_admin = models.BooleanField(default=False) @@ -179,40 +190,40 @@ def __str__(self) -> str: return f"{self.id}" -class GroupResource(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) +class OrganizationResource(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) resource_id = models.ForeignKey("content.Resource", on_delete=models.CASCADE) def __str__(self) -> str: return f"{self.id}" -class GroupTopic(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) - topic_id = models.ForeignKey("content.Topic", on_delete=models.CASCADE) +class OrganizationTask(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) + task_id = models.ForeignKey("content.Task", on_delete=models.CASCADE) + group_id = models.ForeignKey( + "Group", on_delete=models.CASCADE, null=True, blank=True + ) def __str__(self) -> str: return f"{self.id}" -class GroupText(models.Model): - group_id = models.ForeignKey(Group, on_delete=models.CASCADE) +class OrganizationText(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) iso = models.ForeignKey("content.IsoCodeMap", on_delete=models.CASCADE) primary = models.BooleanField(default=False) - description = models.TextField(max_length=500) - get_involved = models.TextField(max_length=500) - donate_prompt = models.TextField(max_length=500) + description = models.TextField(max_length=2500) + get_involved = models.TextField(max_length=500, blank=True) + donate_prompt = models.TextField(max_length=500, blank=True) -class Status(models.Model): - status_type = models.ForeignKey("StatusType", on_delete=models.CASCADE) - org_id = models.ForeignKey( - Organization, on_delete=models.CASCADE, related_name="org_status" - ) - user_id = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) +class OrganizationTopic(models.Model): + org_id = models.ForeignKey(Organization, on_delete=models.CASCADE) + topic_id = models.ForeignKey("content.Topic", on_delete=models.CASCADE) def __str__(self) -> str: - return f"{self.org_id.name} - {self.status_type}" + return f"{self.id}" class StatusType(models.Model): diff --git a/backend/entities/serializers.py b/backend/entities/serializers.py index a01f3c9b7..f3cc732e2 100644 --- a/backend/entities/serializers.py +++ b/backend/entities/serializers.py @@ -10,6 +10,7 @@ GroupImage, GroupMember, GroupResource, + GroupText, GroupTopic, Organization, OrganizationApplication, @@ -18,11 +19,20 @@ OrganizationMember, OrganizationResource, OrganizationTask, + OrganizationText, OrganizationTopic, Status, StatusType, ) +# MARK: Main Tables + + +class GroupSerializer(serializers.ModelSerializer[Group]): + class Meta: + model = Group + fields = "__all__" + class OrganizationSerializer(serializers.ModelSerializer[Organization]): class Meta: @@ -38,8 +48,8 @@ class Meta: "name", "tagline", "icon_url", + "location", "created_by", - "description", "social_links", "is_high_risk", "status", @@ -48,89 +58,98 @@ class Meta: ] -class OrganizationApplicationSerializer( - serializers.ModelSerializer[OrganizationApplication] -): +class StatusSerializer(serializers.ModelSerializer[Status]): class Meta: - model = OrganizationApplication + model = Status fields = "__all__" -class OrganizationEventSerializer(serializers.ModelSerializer[OrganizationEvent]): +# MARK: Bridge Tables + + +class GroupEventSerializer(serializers.ModelSerializer[GroupEvent]): class Meta: - model = OrganizationEvent + model = GroupEvent fields = "__all__" -class OrganizationMemberSerializer(serializers.ModelSerializer[OrganizationMember]): +class GroupImageSerializer(serializers.ModelSerializer[GroupImage]): class Meta: - model = OrganizationMember + model = GroupImage fields = "__all__" -class OrganizationImageSerializer(serializers.ModelSerializer[OrganizationImage]): +class GroupMemberSerializer(serializers.ModelSerializer[GroupMember]): class Meta: - model = OrganizationImage + model = GroupMember fields = "__all__" -class OrganizationResourceSerializer(serializers.ModelSerializer[OrganizationResource]): +class GroupResourceSerializer(serializers.ModelSerializer[GroupResource]): class Meta: - model = OrganizationResource + model = GroupResource fields = "__all__" -class GroupSerializer(serializers.ModelSerializer[Group]): +class GroupTextSerializer(serializers.ModelSerializer[GroupText]): class Meta: - model = Group + model = GroupText fields = "__all__" -class GroupImageSerializer(serializers.ModelSerializer[GroupImage]): +class GroupTopicSerializer(serializers.ModelSerializer[GroupTopic]): class Meta: - model = GroupImage + model = GroupTopic fields = "__all__" -class OrganizationTaskSerializer(serializers.ModelSerializer[OrganizationTask]): +class OrganizationApplicationSerializer( + serializers.ModelSerializer[OrganizationApplication] +): class Meta: - model = OrganizationTask + model = OrganizationApplication fields = "__all__" -class OrganizationTopicSerializer(serializers.ModelSerializer[OrganizationTopic]): +class OrganizationEventSerializer(serializers.ModelSerializer[OrganizationEvent]): class Meta: - model = OrganizationTopic + model = OrganizationEvent fields = "__all__" -class GroupEventSerializer(serializers.ModelSerializer[GroupEvent]): +class OrganizationMemberSerializer(serializers.ModelSerializer[OrganizationMember]): class Meta: - model = GroupEvent + model = OrganizationMember fields = "__all__" -class GroupMemberSerializer(serializers.ModelSerializer[GroupMember]): +class OrganizationImageSerializer(serializers.ModelSerializer[OrganizationImage]): class Meta: - model = GroupMember + model = OrganizationImage fields = "__all__" -class GroupResourceSerializer(serializers.ModelSerializer[GroupResource]): +class OrganizationResourceSerializer(serializers.ModelSerializer[OrganizationResource]): class Meta: - model = GroupResource + model = OrganizationResource fields = "__all__" -class GroupTopicSerializer(serializers.ModelSerializer[GroupTopic]): +class OrganizationTaskSerializer(serializers.ModelSerializer[OrganizationTask]): class Meta: - model = GroupTopic + model = OrganizationTask fields = "__all__" -class StatusSerializer(serializers.ModelSerializer[Status]): +class OrganizationTextSerializer(serializers.ModelSerializer[OrganizationText]): class Meta: - model = Status + model = OrganizationText + fields = "__all__" + + +class OrganizationTopicSerializer(serializers.ModelSerializer[OrganizationTopic]): + class Meta: + model = OrganizationTopic fields = "__all__" diff --git a/backend/entities/urls.py b/backend/entities/urls.py index d73d03c47..0ac217c37 100644 --- a/backend/entities/urls.py +++ b/backend/entities/urls.py @@ -7,21 +7,30 @@ router = DefaultRouter() +# MARK: Main Tables + +router.register(r"groups", views.GroupViewSet) router.register(r"organizations", views.OrganizationViewSet, basename="organization") +router.register(r"status", views.StatusViewSet) + +# MARK: Bridge Tables + +router.register(r"group_events", views.GroupEventViewSet) +router.register(r"group_images", views.GroupImageViewSet) +router.register(r"group_members", views.GroupMemberViewSet) +router.register(r"group_resources", views.GroupResourceViewSet) +router.register(r"group_texts", views.GroupTextViewSet) +router.register(r"group_topics", views.GroupTopicViewSet) + router.register(r"organization_applications", views.OrganizationApplicationViewSet) router.register(r"organization_events", views.OrganizationEventViewSet) router.register(r"organization_images", views.OrganizationImageViewSet) router.register(r"organization_members", views.OrganizationMemberViewSet) router.register(r"organization_resources", views.OrganizationResourceViewSet) -router.register(r"groups", views.GroupViewSet) router.register(r"organization_tasks", views.OrganizationTaskViewSet) +router.register(r"organization_texts", views.OrganizationTextViewSet) router.register(r"organization_topics", views.OrganizationTopicViewSet) -router.register(r"group_events", views.GroupEventViewSet) -router.register(r"group_images", views.GroupImageViewSet) -router.register(r"group_members", views.GroupMemberViewSet) -router.register(r"group_resources", views.GroupResourceViewSet) -router.register(r"group_topics", views.GroupTopicViewSet) -router.register(r"status", views.StatusViewSet) + router.register(r"status_type", views.StatusTypeViewSet) urlpatterns = [ diff --git a/backend/entities/views.py b/backend/entities/views.py index c14e77bd4..f8015e652 100644 --- a/backend/entities/views.py +++ b/backend/entities/views.py @@ -15,6 +15,7 @@ GroupImage, GroupMember, GroupResource, + GroupText, GroupTopic, Organization, OrganizationApplication, @@ -23,6 +24,7 @@ OrganizationMember, OrganizationResource, OrganizationTask, + OrganizationText, OrganizationTopic, Status, StatusType, @@ -33,6 +35,7 @@ GroupMemberSerializer, GroupResourceSerializer, GroupSerializer, + GroupTextSerializer, GroupTopicSerializer, OrganizationApplicationSerializer, OrganizationEventSerializer, @@ -41,11 +44,72 @@ OrganizationResourceSerializer, OrganizationSerializer, OrganizationTaskSerializer, + OrganizationTextSerializer, OrganizationTopicSerializer, StatusSerializer, StatusTypeSerializer, ) +# MARK: Main Tables + + +class GroupViewSet(viewsets.ModelViewSet[Group]): + queryset = Group.objects.all() + serializer_class = GroupSerializer + pagination_class = CustomPagination + + 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}"} + 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) + + return Response(serializer.data, status=status.HTTP_200_OK) + + def partial_update(self, request: Request, *args: str, **kwargs: int) -> Response: + group = self.queryset.filter(id=kwargs["pk"]).first() + + if group is None: + 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"}, + status.HTTP_401_UNAUTHORIZED, + ) + + 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: + group = self.queryset.filter(id=kwargs["pk"]).first() + + if group is None: + 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 + ) + class OrganizationViewSet(viewsets.ModelViewSet[Organization]): queryset = Organization.objects.all() @@ -67,8 +131,7 @@ def create(self, request: Request) -> Response: return Response(serializer.data, status=status.HTTP_201_CREATED) def retrieve(self, request: Request, pk: str | None = None) -> Response: - org = self.queryset.filter(id=pk).first() - if org: + if org := self.queryset.filter(id=pk).first(): serializer = self.get_serializer(org) return Response(serializer.data, status=status.HTTP_200_OK) @@ -141,32 +204,18 @@ def destroy(self, request: Request, pk: str | None = None) -> Response: ) -class OrganizationApplicationViewSet(viewsets.ModelViewSet[OrganizationApplication]): - queryset = OrganizationApplication.objects.all() - serializer_class = OrganizationApplicationSerializer - pagination_class = CustomPagination - - -class OrganizationEventViewSet(viewsets.ModelViewSet[OrganizationEvent]): - queryset = OrganizationEvent.objects.all() - serializer_class = OrganizationEventSerializer - pagination_class = CustomPagination - - -class OrganizationMemberViewSet(viewsets.ModelViewSet[OrganizationMember]): - queryset = OrganizationMember.objects.all() - serializer_class = OrganizationMemberSerializer +class StatusViewSet(viewsets.ModelViewSet[Status]): + queryset = Status.objects.all() + serializer_class = StatusSerializer pagination_class = CustomPagination -class OrganizationImageViewSet(viewsets.ModelViewSet[OrganizationImage]): - queryset = OrganizationImage.objects.all() - serializer_class = OrganizationImageSerializer +# MARK: Bridge Tables -class OrganizationResourceViewSet(viewsets.ModelViewSet[OrganizationResource]): - queryset = OrganizationResource.objects.all() - serializer_class = OrganizationResourceSerializer +class GroupEventViewSet(viewsets.ModelViewSet[GroupEvent]): + queryset = GroupEvent.objects.all() + serializer_class = GroupEventSerializer pagination_class = CustomPagination @@ -176,103 +225,74 @@ class GroupImageViewSet(viewsets.ModelViewSet[GroupImage]): pagination_class = CustomPagination -class GroupViewSet(viewsets.ModelViewSet[Group]): - queryset = Group.objects.all() - serializer_class = GroupSerializer +class GroupMemberViewSet(viewsets.ModelViewSet[GroupMember]): + queryset = GroupMember.objects.all() + serializer_class = GroupMemberSerializer pagination_class = CustomPagination - 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}"} - 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) +class GroupResourceViewSet(viewsets.ModelViewSet[GroupResource]): + queryset = GroupResource.objects.all() + serializer_class = GroupResourceSerializer + pagination_class = CustomPagination - return Response(serializer.data, status=status.HTTP_200_OK) - def partial_update(self, request: Request, *args: str, **kwargs: int) -> Response: - group = self.queryset.filter(id=kwargs["pk"]).first() +class GroupTextViewSet(viewsets.ModelViewSet[GroupText]): + queryset = GroupText.objects.all() + serializer_class = GroupTextSerializer + pagination_class = CustomPagination - if group is None: - 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"}, - status.HTTP_401_UNAUTHORIZED, - ) - 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) +class GroupTopicViewSet(viewsets.ModelViewSet[GroupTopic]): + queryset = GroupTopic.objects.all() + serializer_class = GroupTopicSerializer + pagination_class = CustomPagination - def destroy(self, request: Request, *args: str, **kwargs: int) -> Response: - group = self.queryset.filter(id=kwargs["pk"]).first() - if group is None: - 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 - ) +class OrganizationApplicationViewSet(viewsets.ModelViewSet[OrganizationApplication]): + queryset = OrganizationApplication.objects.all() + serializer_class = OrganizationApplicationSerializer + pagination_class = CustomPagination -class OrganizationTaskViewSet(viewsets.ModelViewSet[OrganizationTask]): - queryset = OrganizationTask.objects.all() - serializer_class = OrganizationTaskSerializer +class OrganizationEventViewSet(viewsets.ModelViewSet[OrganizationEvent]): + queryset = OrganizationEvent.objects.all() + serializer_class = OrganizationEventSerializer pagination_class = CustomPagination -class OrganizationTopicViewSet(viewsets.ModelViewSet[OrganizationTopic]): - queryset = OrganizationTopic.objects.all() - serializer_class = OrganizationTopicSerializer +class OrganizationMemberViewSet(viewsets.ModelViewSet[OrganizationMember]): + queryset = OrganizationMember.objects.all() + serializer_class = OrganizationMemberSerializer pagination_class = CustomPagination -class GroupEventViewSet(viewsets.ModelViewSet[GroupEvent]): - queryset = GroupEvent.objects.all() - serializer_class = GroupEventSerializer - pagination_class = CustomPagination +class OrganizationImageViewSet(viewsets.ModelViewSet[OrganizationImage]): + queryset = OrganizationImage.objects.all() + serializer_class = OrganizationImageSerializer -class GroupMemberViewSet(viewsets.ModelViewSet[GroupMember]): - queryset = GroupMember.objects.all() - serializer_class = GroupMemberSerializer +class OrganizationResourceViewSet(viewsets.ModelViewSet[OrganizationResource]): + queryset = OrganizationResource.objects.all() + serializer_class = OrganizationResourceSerializer pagination_class = CustomPagination -class GroupResourceViewSet(viewsets.ModelViewSet[GroupResource]): - queryset = GroupResource.objects.all() - serializer_class = GroupResourceSerializer +class OrganizationTaskViewSet(viewsets.ModelViewSet[OrganizationTask]): + queryset = OrganizationTask.objects.all() + serializer_class = OrganizationTaskSerializer pagination_class = CustomPagination -class GroupTopicViewSet(viewsets.ModelViewSet[GroupTopic]): - queryset = GroupTopic.objects.all() - serializer_class = GroupTopicSerializer +class OrganizationTextViewSet(viewsets.ModelViewSet[OrganizationText]): + queryset = OrganizationText.objects.all() + serializer_class = OrganizationTextSerializer pagination_class = CustomPagination -class StatusViewSet(viewsets.ModelViewSet[Status]): - queryset = Status.objects.all() - serializer_class = StatusSerializer +class OrganizationTopicViewSet(viewsets.ModelViewSet[OrganizationTopic]): + queryset = OrganizationTopic.objects.all() + serializer_class = OrganizationTopicSerializer pagination_class = CustomPagination diff --git a/backend/events/admin.py b/backend/events/admin.py index 78472e867..276e7e2c3 100644 --- a/backend/events/admin.py +++ b/backend/events/admin.py @@ -14,7 +14,14 @@ Role, ) +# MARK: Main Tables + admin.site.register(Event) +admin.site.register(Format) +admin.site.register(Role) + +# MARK: Bridge Tables + admin.site.register(EventAttendee) admin.site.register(EventAttendeeStatus) admin.site.register(EventResource) @@ -23,5 +30,3 @@ admin.site.register(EventText) admin.site.register(EventTopic) admin.site.register(EventFormat) -admin.site.register(Format) -admin.site.register(Role) diff --git a/backend/events/factories.py b/backend/events/factories.py index 8200a8afc..80ab35ca5 100644 --- a/backend/events/factories.py +++ b/backend/events/factories.py @@ -15,6 +15,8 @@ Role, ) +# MARK: Main Tables + class EventFactory(factory.django.DjangoModelFactory): class Meta: @@ -58,6 +60,9 @@ class Meta: deprecation_date = factory.Faker("future_date", end_date="+30d") +# MARK: Bridge Tables + + class EventAttendeeFactory(factory.django.DjangoModelFactory): class Meta: model = EventAttendee @@ -68,19 +73,19 @@ class Meta: attendee_status = factory.SubFactory("events.factories.EventAttendeeStatusFactory") -class EventFormatFactory(factory.django.DjangoModelFactory): +class EventAttendeeStatusFactory(factory.django.DjangoModelFactory): class Meta: - model = EventFormat + model = EventAttendeeStatus - event_id = factory.SubFactory(EventFactory) - format_id = factory.SubFactory(FormatFactory) + status_name = factory.Faker("word") -class EventAttendeeStatusFactory(factory.django.DjangoModelFactory): +class EventFormatFactory(factory.django.DjangoModelFactory): class Meta: - model = EventAttendeeStatus + model = EventFormat - status_name = factory.Faker("word") + event_id = factory.SubFactory(EventFactory) + format_id = factory.SubFactory(FormatFactory) class EventResourceFactory(factory.django.DjangoModelFactory): diff --git a/backend/events/models.py b/backend/events/models.py index a36e9f883..8317857a3 100644 --- a/backend/events/models.py +++ b/backend/events/models.py @@ -9,6 +9,8 @@ from backend.mixins.models import CreationDeletionMixin +# MARK: Main Tables + class Event(CreationDeletionMixin): id = models.UUIDField(primary_key=True, default=uuid4, editable=False) @@ -23,8 +25,6 @@ class Event(CreationDeletionMixin): "content.Image", on_delete=models.CASCADE, blank=True, null=True ) type = models.CharField(max_length=255) - description = models.TextField(max_length=500) - get_involved_text = models.TextField(max_length=500) online_location_link = 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) @@ -65,6 +65,9 @@ def __str__(self) -> str: return self.name +# MARK: Bridge Tables + + class EventAttendee(models.Model): event_id = models.ForeignKey(Event, on_delete=models.CASCADE) user_id = models.ForeignKey("authentication.UserModel", on_delete=models.CASCADE) @@ -77,14 +80,6 @@ def __str__(self) -> str: return f"{self.user_id} - {self.event_id}" -class EventFormat(models.Model): - event_id = models.ForeignKey(Event, on_delete=models.CASCADE) - format_id = models.ForeignKey("Format", on_delete=models.CASCADE) - - def __str__(self) -> str: - return f"{self.id}" - - class EventAttendeeStatus(models.Model): id = models.IntegerField(primary_key=True) status_name = models.CharField(max_length=255) @@ -93,6 +88,14 @@ def __str__(self) -> str: return self.status_name +class EventFormat(models.Model): + event_id = models.ForeignKey(Event, on_delete=models.CASCADE) + format_id = models.ForeignKey("Format", on_delete=models.CASCADE) + + def __str__(self) -> str: + return f"{self.id}" + + class EventResource(models.Model): event_id = models.ForeignKey(Event, on_delete=models.CASCADE) resource_id = models.ForeignKey("content.Resource", on_delete=models.CASCADE) @@ -109,6 +112,14 @@ def __str__(self) -> str: return f"{self.id}" +class EventTag(models.Model): + event_id = models.ForeignKey(Event, on_delete=models.CASCADE) + tag_id = models.ForeignKey("content.Tag", on_delete=models.CASCADE) + + def __str__(self) -> str: + return f"{self.id}" + + class EventTask(models.Model): event_id = models.ForeignKey(Event, on_delete=models.CASCADE) task_id = models.ForeignKey("content.Task", on_delete=models.CASCADE) @@ -124,7 +135,7 @@ class EventText(models.Model): ) primary = models.BooleanField() description = models.TextField(max_length=500) - get_involved = models.TextField(max_length=500) + get_involved = models.TextField(max_length=500, blank=True) def __str__(self) -> str: return f"{self.id}" @@ -136,11 +147,3 @@ class EventTopic(models.Model): def __str__(self) -> str: return f"{self.id}" - - -class EventTag(models.Model): - event_id = models.ForeignKey(Event, on_delete=models.CASCADE) - tag_id = models.ForeignKey("content.Tag", on_delete=models.CASCADE) - - def __str__(self) -> str: - return f"{self.id}" diff --git a/backend/events/serializers.py b/backend/events/serializers.py index 5668edcf1..144c1f59a 100644 --- a/backend/events/serializers.py +++ b/backend/events/serializers.py @@ -28,6 +28,8 @@ Role, ) +# MARK: Main Tables + class EventSerializer(serializers.ModelSerializer[Event]): class Meta: @@ -68,21 +70,24 @@ def validate(self, data: Dict[str, Union[str, int]]) -> Dict[str, Union[str, int return data +# MARK: Bridge Tables + + class EventAttendeeSerializer(serializers.ModelSerializer[EventAttendee]): class Meta: model = EventAttendee fields = "__all__" -class EventFormatSerializer(serializers.ModelSerializer[EventFormat]): +class EventAttendeeStatusSerializer(serializers.ModelSerializer[EventAttendeeStatus]): class Meta: - model = EventFormat + model = EventAttendeeStatus fields = "__all__" -class EventAttendeeStatusSerializer(serializers.ModelSerializer[EventAttendeeStatus]): +class EventFormatSerializer(serializers.ModelSerializer[EventFormat]): class Meta: - model = EventAttendeeStatus + model = EventFormat fields = "__all__" @@ -98,6 +103,12 @@ class Meta: fields = "__all__" +class EventTagSerializer(serializers.ModelSerializer[EventTag]): + class Meta: + model = EventTag + fields = "__all__" + + class EventTaskSerializer(serializers.ModelSerializer[EventTask]): class Meta: model = EventTask @@ -114,9 +125,3 @@ class EventTopicSerializer(serializers.ModelSerializer[EventTopic]): class Meta: model = EventTopic fields = "__all__" - - -class EventTagSerializer(serializers.ModelSerializer[EventTag]): - class Meta: - model = EventTag - fields = "__all__" diff --git a/backend/events/urls.py b/backend/events/urls.py index bd38a731e..59ca90b80 100644 --- a/backend/events/urls.py +++ b/backend/events/urls.py @@ -6,15 +6,22 @@ app_name = "events" router = DefaultRouter() + +# MARK: Main Tables + router.register(r"events", views.EventViewSet) router.register(r"formats", views.FormatViewSet) router.register(r"roles", views.RoleViewSet) + +# MARK: Bridge Tables + router.register(r"event_attendees", views.EventAttendeeViewSet) router.register(r"event_formats", views.EventFormatViewSet) router.register(r"event_attendee_statuses", views.EventAttendeeStatusViewSet) router.register(r"event_resources", views.EventResourceViewSet) router.register(r"event_roles", views.EventRoleViewSet) router.register(r"event_tasks", views.EventTaskViewSet) +router.register(r"event_texts", views.EventTextViewSet) router.register(r"event_topics", views.EventTopicViewSet) urlpatterns = [ diff --git a/backend/events/views.py b/backend/events/views.py index e87273fc9..d1de501d4 100644 --- a/backend/events/views.py +++ b/backend/events/views.py @@ -33,6 +33,8 @@ RoleSerializer, ) +# MARK: Main Tables + class EventViewSet(viewsets.ModelViewSet[Event]): queryset = Event.objects.all() @@ -53,24 +55,27 @@ class RoleViewSet(viewsets.ModelViewSet[Role]): pagination_class = CustomPagination +# MARK: Bridge Tables + + class EventAttendeeViewSet(viewsets.ModelViewSet[EventAttendee]): queryset = EventAttendee.objects.all() serializer_class = EventAttendeeSerializer pagination_class = CustomPagination -class EventFormatViewSet(viewsets.ModelViewSet[EventFormat]): - queryset = EventFormat.objects.all() - serializer_class = EventFormatSerializer - pagination_class = CustomPagination - - class EventAttendeeStatusViewSet(viewsets.ModelViewSet[EventAttendeeStatus]): queryset = EventAttendeeStatus.objects.all() serializer_class = EventAttendeeStatusSerializer pagination_class = CustomPagination +class EventFormatViewSet(viewsets.ModelViewSet[EventFormat]): + queryset = EventFormat.objects.all() + serializer_class = EventFormatSerializer + pagination_class = CustomPagination + + class EventResourceViewSet(viewsets.ModelViewSet[EventResource]): queryset = EventResource.objects.all() serializer_class = EventResourceSerializer diff --git a/backend/fixtures/iso_code_map.json b/backend/fixtures/iso_code_map.json new file mode 100644 index 000000000..9e221b84f --- /dev/null +++ b/backend/fixtures/iso_code_map.json @@ -0,0 +1,9 @@ +[ + { + "model": "content.isocodemap", + "pk": 1, + "fields": { + "code": "en" + } + } +] diff --git a/backend/fixtures/superuser.json b/backend/fixtures/superuser.json index 9000d4e0a..1b22a93a1 100644 --- a/backend/fixtures/superuser.json +++ b/backend/fixtures/superuser.json @@ -16,11 +16,12 @@ "description": "", "verified": false, "verification_method": "", - "verification_partner": "7664552d-e9cb-49f8-9683-a58acdd4f504", + "verification_partner": null, "email": "admin@activist.org", "is_high_risk": false, "is_active": true, "is_admin": false, + "is_confirmed": true, "groups": [], "user_permissions": [] } diff --git a/docker-compose.yml b/docker-compose.yml index 23ecf97f9..20c4bf700 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,8 @@ services: command: sh -c "python manage.py makemigrations && python manage.py migrate && python manage.py loaddata fixtures/superuser.json && + python manage.py loaddata fixtures/status_types.json && + python manage.py loaddata fixtures/iso_code_map.json && python manage.py runserver 0.0.0.0:${BACKEND_PORT}" ports: - "${BACKEND_PORT}:${BACKEND_PORT}" diff --git a/frontend/components/card/CardAbout.vue b/frontend/components/card/CardAbout.vue deleted file mode 100644 index d14df78c6..000000000 --- a/frontend/components/card/CardAbout.vue +++ /dev/null @@ -1,256 +0,0 @@ - - - diff --git a/frontend/components/card/CardConnect.vue b/frontend/components/card/CardConnect.vue index e958ca1f5..b790cb187 100644 --- a/frontend/components/card/CardConnect.vue +++ b/frontend/components/card/CardConnect.vue @@ -8,13 +8,13 @@ class="cursor-pointer break-all rounded-lg p-1 text-light-text transition-all hover:text-light-distinct-text dark:text-dark-text dark:hover:text-dark-distinct-text" > import { Popover, PopoverButton, PopoverPanel } from "@headlessui/vue"; +import type { Organization } from "~/types/entities/organization"; import { IconMap } from "~/types/icon-map"; const props = defineProps<{ - socialLinks?: string[]; - userIsAdmin?: boolean; + pageType: "organization" | "group" | "event" | "other"; }>(); +const { userIsSignedIn } = useUser(); +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 organizationStore = useOrganizationStore(); +let organization: Organization; +const group = useGroupStore(); +const event = useEventStore(); + +if (props.pageType == "organization") { + await organizationStore.fetchByID(id); + organization = organizationStore.organization; +} else if (props.pageType == "group") { + await group.fetchByID(idGroup); +} else if (props.pageType == "event") { + await event.fetchByID(id); +} + const editModeEnabled = ref(false); -const socialLinksRef = computed(() => props.socialLinks); +const socialLinksRef = computed(() => { + if (props.pageType == "organization") { + return organization.socialLinks; + } else if (props.pageType == "group") { + return group.socialLinks; + } else if (props.pageType == "event") { + return event.socialLinks; + } else { + return [""]; + } +}); const toggleEditMode = () => { editModeEnabled.value = !editModeEnabled.value; diff --git a/frontend/components/card/CardDetails.vue b/frontend/components/card/CardDetails.vue index d0784750d..935b032f2 100644 --- a/frontend/components/card/CardDetails.vue +++ b/frontend/components/card/CardDetails.vue @@ -8,15 +8,15 @@ {{ $t("components.card-details.header") }} - @@ -44,21 +44,12 @@ diff --git a/frontend/components/card/CardOrgApplicationVote.vue b/frontend/components/card/CardOrgApplicationVote.vue index f26d29082..634931469 100644 --- a/frontend/components/card/CardOrgApplicationVote.vue +++ b/frontend/components/card/CardOrgApplicationVote.vue @@ -55,8 +55,8 @@ diff --git a/frontend/components/card/about/CardAboutGroup.vue b/frontend/components/card/about/CardAboutGroup.vue new file mode 100644 index 000000000..d6804c5c4 --- /dev/null +++ b/frontend/components/card/about/CardAboutGroup.vue @@ -0,0 +1,131 @@ + + + diff --git a/frontend/components/card/about/CardAboutOrganization.vue b/frontend/components/card/about/CardAboutOrganization.vue new file mode 100644 index 000000000..3b5d368d0 --- /dev/null +++ b/frontend/components/card/about/CardAboutOrganization.vue @@ -0,0 +1,130 @@ + + + diff --git a/frontend/components/card/discussion/CardDiscussion.vue b/frontend/components/card/discussion/CardDiscussion.vue index 04d14947d..7e1f1fdde 100644 --- a/frontend/components/card/discussion/CardDiscussion.vue +++ b/frontend/components/card/discussion/CardDiscussion.vue @@ -78,7 +78,7 @@ diff --git a/frontend/components/card/get-involved/CardGetInvolvedGroup.vue b/frontend/components/card/get-involved/CardGetInvolvedGroup.vue new file mode 100644 index 000000000..197e6f538 --- /dev/null +++ b/frontend/components/card/get-involved/CardGetInvolvedGroup.vue @@ -0,0 +1,63 @@ + + + diff --git a/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue b/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue new file mode 100644 index 000000000..e6b8baa3b --- /dev/null +++ b/frontend/components/card/get-involved/CardGetInvolvedOrganization.vue @@ -0,0 +1,84 @@ + + + diff --git a/frontend/components/card/search-result/CardSearchResult.vue b/frontend/components/card/search-result/CardSearchResult.vue index d1e5ba09e..1b989cd14 100644 --- a/frontend/components/card/search-result/CardSearchResult.vue +++ b/frontend/components/card/search-result/CardSearchResult.vue @@ -166,7 +166,7 @@ :link="onlineLocation" label="components.meta-tag-video.view-video" /> - +
@@ -177,7 +177,7 @@ :link="onlineLocation" label="components.meta-tag-video.view-video" /> - +
import { useLinkURL } from "~/composables/useLinkURL"; -import type { Event } from "~/types/event"; -import type { Group } from "~/types/group"; +import type { User } from "~/types/auth/user"; +import type { Resource } from "~/types/content/resource"; +import type { Group } from "~/types/entities/group"; +import type { Organization } from "~/types/entities/organization"; +import type { Event } from "~/types/events/event"; import { IconMap } from "~/types/icon-map"; -import type { Organization } from "~/types/organization"; -import type { Resource } from "~/types/resource"; -import type { User } from "~/types/user"; const props = defineProps<{ organization?: Organization; diff --git a/frontend/components/card/search-result/CardSearchResultEvent.vue b/frontend/components/card/search-result/CardSearchResultEvent.vue index 7fd33e7c4..d140b9105 100644 --- a/frontend/components/card/search-result/CardSearchResultEvent.vue +++ b/frontend/components/card/search-result/CardSearchResultEvent.vue @@ -7,7 +7,7 @@ diff --git a/frontend/components/form/FormTextField.vue b/frontend/components/form/text/FormTextInput.vue similarity index 100% rename from frontend/components/form/FormTextField.vue rename to frontend/components/form/text/FormTextInput.vue diff --git a/frontend/components/header/HeaderAppPage.vue b/frontend/components/header/HeaderAppPage.vue index 8d6233003..9f7cac34e 100644 --- a/frontend/components/header/HeaderAppPage.vue +++ b/frontend/components/header/HeaderAppPage.vue @@ -1,10 +1,5 @@ diff --git a/frontend/components/modal/edit/about/ModalEditAboutGroup.vue b/frontend/components/modal/edit/about/ModalEditAboutGroup.vue new file mode 100644 index 000000000..c95debec1 --- /dev/null +++ b/frontend/components/modal/edit/about/ModalEditAboutGroup.vue @@ -0,0 +1,90 @@ +