diff --git a/src/_main_/utils/common.py b/src/_main_/utils/common.py index e71cc2b81..6cb88ebc4 100644 --- a/src/_main_/utils/common.py +++ b/src/_main_/utils/common.py @@ -89,20 +89,20 @@ def get_request_contents(request, **kwargs): def parse_list(d): try: - tmp = [] if isinstance(d, str): return d.strip().split(",") if d else [] - elif isinstance(d, dict): - tmp = list(d.values()) - res = [] - for i in tmp: - if i.isnumeric(): - res.append(i) - return res + if isinstance(d, dict): + return [value for value in d.values() if str(value).isnumeric()] + if isinstance(d, list): + if d and isinstance(d[0], dict): + return d + return [item for item in d if str(item).isnumeric()] + + return [] except Exception as e: - log.exception(e) + log.exception("Error in parse_list: %s", e) return [] def parse_dict(d: object) -> object: diff --git a/src/api/handlers/custom_pages.py b/src/api/handlers/custom_pages.py new file mode 100644 index 000000000..5410080da --- /dev/null +++ b/src/api/handlers/custom_pages.py @@ -0,0 +1,143 @@ +from _main_.utils.context import Context +from _main_.utils.massenergize_response import MassenergizeResponse +from _main_.utils.route_handler import RouteHandler +from api.decorators import admins_only +from api.services.custom_pages import CustomPagesService + + +class CustomPagesHandler(RouteHandler): + + def __init__(self): + super().__init__() + self.service = CustomPagesService() + self.registerRoutes() + + def registerRoutes(self): + self.add("/community.custom.pages.create", self.create_community_custom_page) + self.add("/community.custom.pages.update", self.update_community_custom_page) + self.add("/community.custom.pages.delete", self.delete_community_custom_page) + self.add("/community.custom.pages.share", self.share_community_custom_page) + self.add("/community.custom.pages.info", self.community_custom_page_info) + self.add("/community.custom.pages.list", self.list_community_custom_pages) + self.add("/custom.page.publish", self.publish_custom_page) + + @admins_only + def create_community_custom_page(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("title", str, is_required=True) + self.validator.expect("community_id", str, is_required=True) + self.validator.expect("content", list, is_required=False) + self.validator.expect("audience", "str_list", is_required=False) + self.validator.expect("sharing_type", str, is_required=False) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.create_community_custom_page(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + @admins_only + def update_community_custom_page(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("id", str, is_required=True) + self.validator.expect("title", str, is_required=False) + self.validator.expect("content", list, is_required=False) + self.validator.expect("audience", "str_list", is_required=False) + self.validator.expect("sharing_type", str, is_required=False) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.update_community_custom_page(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + @admins_only + def delete_community_custom_page(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("id", str, is_required=True) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.delete_community_custom_page(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + @admins_only + def share_community_custom_page(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("community_page_id", str, is_required=True) + self.validator.expect("community_ids", "str_list", is_required=True) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.share_community_custom_page(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + + def community_custom_page_info(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("id", str, is_required=True) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.community_custom_page_info(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + @admins_only + def list_community_custom_pages(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("community_id", "id", is_required=True) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.list_community_custom_pages(context, args) + if err: + return err + return MassenergizeResponse(data=page) + + @admins_only + def publish_custom_page(self, request): + context: Context = request.context + args: dict = context.args + + self.validator.expect("id", str, is_required=True) + + args, err = self.validator.verify(args, strict=True) + if err: + return err + + page, err = self.service.publish_custom_page(context, args) + if err: + return err + return MassenergizeResponse(data=page) \ No newline at end of file diff --git a/src/api/services/custom_pages.py b/src/api/services/custom_pages.py new file mode 100644 index 000000000..937fa9524 --- /dev/null +++ b/src/api/services/custom_pages.py @@ -0,0 +1,89 @@ +from typing import Tuple +from _main_.utils.common import serialize, serialize_all +from _main_.utils.context import Context +from _main_.utils.massenergize_errors import MassEnergizeAPIError +from api.store.custom_pages import CustomPagesStore + + +class CustomPagesService: + """ + Service Layer for all the custom pages + """ + + + def __init__(self): + self.store = CustomPagesStore() + + def create_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page, err = self.store.create_community_custom_page(context, args) + if err: + return None, err + + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + def update_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page, err = self.store.update_community_custom_page(context, args) + if err: + return None, err + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + def delete_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + + page, err = self.store.delete_community_custom_page(context, args) + + if err: + return None, err + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + def list_community_custom_pages(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + pages, err = self.store.list_community_custom_pages(context, args) + if err: + return None, err + return serialize_all(pages), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + + def community_custom_page_info(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + + page, err = self.store.community_custom_page_info(context, args) + if err: + return None, err + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + + def share_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page, err = self.store.share_community_custom_page(context, args) + if err: + return None, err + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + + def publish_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page, err = self.store.publish_custom_page(context, args) + if err: + return None, err + return serialize(page), None + except Exception as e: + return None, MassEnergizeAPIError(str(e)) + + + + \ No newline at end of file diff --git a/src/api/store/custom_pages.py b/src/api/store/custom_pages.py new file mode 100644 index 000000000..a3499c073 --- /dev/null +++ b/src/api/store/custom_pages.py @@ -0,0 +1,222 @@ +from typing import Tuple +from _main_.utils.context import Context +from _main_.utils.massenergize_errors import CustomMassenergizeError, MassEnergizeAPIError, NotAuthorizedError +from _main_.utils.massenergize_logger import log +from api.store.utils import get_user_from_context +from api.utils.api_utils import create_unique_slug, is_admin_of_community +from database.models import Community, CommunityCustomPage, CommunityCustomPageShare, CustomPage, UserProfile + + +class CustomPagesStore: + def __init__(self): + self.name = "Custom Page Store/DB" + + def create_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + community_id = args.pop('community_id', None) + title = args.pop('title', None) + user = get_user_from_context(context) + audience = args.pop('audience', None) + sharing_type = args.pop('sharing_type', None) + slug = args.pop('slug', None) + content = args.pop('content', None) + + if not community_id: + return None, CustomMassenergizeError("Missing community_id") + + if not title: + return None, CustomMassenergizeError("Missing title") + + if not user: + return None, NotAuthorizedError() + + community = Community.objects.get(id=community_id) + if not community: + return None, CustomMassenergizeError("Invalid community_id") + + if not slug: + slug = create_unique_slug(title, CustomPage, "slug", community.subdomain) + + page = CustomPage.objects.create(title=title, user=user, slug=slug, content=content) + + community_custom_pages = CommunityCustomPage.objects.create(community=community, custom_page=page) + + if audience: + community_custom_pages.audience.set(audience) + + if sharing_type: + community_custom_pages.sharing_type = sharing_type + + community_custom_pages.save() + + return community_custom_pages, None + + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + def update_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page_id = args.pop('id', None) + title = args.pop('title', None) + audience = args.pop('audience', None) + sharing_type = args.pop('sharing_type', None) + slug = args.pop('slug', None) + content = args.pop('content', None) + + page = CustomPage.objects.get(id=page_id) + if not page: + return None, CustomMassenergizeError("Invalid page_id") + + community_custom_page = CommunityCustomPage.objects.get(custom_page=page) + + if not community_custom_page: + return None, CustomMassenergizeError("Invalid page_id") + + + if (title and page.title != title) or (slug and page.slug != slug): + slug = create_unique_slug(title, CustomPage, "slug", community_custom_page.community.subdomain) if not slug else slug + page.title = title + + page.slug = slug + + if content: + page.content = content + + + if not is_admin_of_community(context, community_custom_page.community.id): + return None, NotAuthorizedError() + + page.save() + + if audience: + community_custom_page.audience.set(audience) + if sharing_type: + community_custom_page.sharing_type = sharing_type + + community_custom_page.save() + + return community_custom_page, None + + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + + + def delete_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page_id = args.pop("id") + + page = CustomPage.objects.get(id=page_id) + + if not page: + return None, CustomMassenergizeError("Invalid page_id") + + community_custom_page = CommunityCustomPage.objects.get(custom_page=page) + + if not community_custom_page: + return None, CustomMassenergizeError("Invalid page_id") + + if not is_admin_of_community(context, community_custom_page.community.id): + return None, NotAuthorizedError() + + community_custom_page.is_deleted = True + community_custom_page.save() + + page.is_deleted = True + page.save() + + return page, None + + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + + def list_community_custom_pages(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + community_id = args.pop('community_id', None) + if not community_id: + return None, CustomMassenergizeError("Missing community_id") + + community_pages = CommunityCustomPage.objects.filter(community__id=community_id, is_deleted=False) + return community_pages, None + + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + + + def community_custom_page_info(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page_id = args.pop('id', None) + + if not page_id: + return None, CustomMassenergizeError("Missing id") + + page = CustomPage.objects.get(id=page_id, is_deleted=False) + if not page: + return None, CustomMassenergizeError("Invalid id") + + return page, None + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + + def share_community_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + community_page_id = args.pop('community_page_id', None) + audience_communities_id= args.pop('community_ids', None) + + if not community_page_id: + return None, CustomMassenergizeError("Missing community_page_id") + + if not audience_communities_id: + return None, CustomMassenergizeError("Missing community_ids") + + community_page = CommunityCustomPage.objects.get(id=community_page_id, is_deleted=False) + if not community_page: + return None, CustomMassenergizeError("Invalid community_page_id") + + + audience_communities = Community.objects.filter(id__in=audience_communities_id, is_deleted=False) + + page_shares = [] + + for community_id in audience_communities: + page_shares.append( + CommunityCustomPageShare( + community=community_id, + community_page=community_page + ) + ) + + CommunityCustomPageShare.objects.bulk_create(page_shares, ignore_conflicts=True) + + return community_page, None + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + + + def publish_custom_page(self, context: Context, args) -> Tuple[dict, MassEnergizeAPIError]: + try: + page_id = args.pop('id', None) + if not page_id: + return None, CustomMassenergizeError("Missing page_id") + + page = CustomPage.objects.get(id=page_id, is_deleted=False) + if not page: + return None, CustomMassenergizeError("Invalid page_id") + + page.create_version() + + return page, None + + except Exception as e: + log.exception(e) + return None, CustomMassenergizeError(e) + \ No newline at end of file diff --git a/src/api/tests/common.py b/src/api/tests/common.py index 4accedadf..220400836 100644 --- a/src/api/tests/common.py +++ b/src/api/tests/common.py @@ -9,6 +9,7 @@ from _main_.utils.common import parse_datetime_to_aware from _main_.utils.utils import load_json +from database.utils.settings.model_constants.enums import SharingType from ..store.utils import unique_media_filename from _main_.settings import SECRET_KEY from _main_.utils.feature_flags.FeatureFlagConstants import FeatureFlagConstants @@ -16,7 +17,9 @@ Action, Community, CommunityAdminGroup, + CommunityCustomPage, CommunityMember, + CustomPage, Event, FeatureFlag, Footage, @@ -504,5 +507,31 @@ def make_tag(**kwargs): **kwargs, "name": kwargs.get("name") or f"New Tag-{datetime.now().timestamp()}", }) + +def make_community_custom_page(**kwargs): + community = kwargs.pop("community") or makeCommunity() + title = kwargs.get("title") or f"New Custom Page-{datetime.now().timestamp()}" + audience = kwargs.pop("audience", []) + sharing_type = kwargs.pop("sharing_type", SharingType.OPEN_TO.value[0]) + user = kwargs.pop("user", makeUser()) + + page = CustomPage.objects.create(**{ + **kwargs, + "title": title, + "user": user, + }) + + community_custom_page = CommunityCustomPage.objects.create( + community=community, custom_page=page, + sharing_type=sharing_type + ) + if audience: + community_custom_page.audience.set(audience) + + return page, community_custom_page + + + + \ No newline at end of file diff --git a/src/api/tests/integration/test_custom_pages.py b/src/api/tests/integration/test_custom_pages.py new file mode 100644 index 000000000..7b5c5247f --- /dev/null +++ b/src/api/tests/integration/test_custom_pages.py @@ -0,0 +1,391 @@ +from _main_.utils.utils import Console +from api.tests.common import ( + createImage, + createUsers, + make_community_custom_page, + makeCommunity, + makeUser, + signinAs, +) +from django.test import Client, TestCase + +from database.utils.settings.model_constants.enums import SharingType + + +class CustomPagesIntegrationTestCase(TestCase): + @staticmethod + def setUpClass(): + pass + + def setUp(self): + self.client = Client() + self.USER, self.CADMIN, self.SADMIN = createUsers() + self.user = makeUser() + + self.COMMUNITY_1 = makeCommunity() + self.COMMUNITY_2 = makeCommunity() + self.COMMUNITY_3 = makeCommunity() + self.content = ( + [ + { + "block": { + "id": 1731409607781, + "name": "Paragraph", + "icon": "fa-paragraph", + "key": "paragraph", + "template": { + "element": { + "id": 1731409403366, + "type": "p", + "props": { + "style": {"padding": 10, "margin": 0}, + "text": "Piano scales are one of the first things you learn as a beginner piano player, but why are they so important? Well, playing your C major scale up and down isn’t just about practicing your technique; scales are a foundational musical concept. Understanding scales means you’ll understand key signatures and chords, which form the building blocks of Western music. In this post, we’ll discuss why scales are important, break down the different types of scales, and show you ways to apply these scales to your piano playing.", + }, + } + }, + } + } + ], + ) + + self.p1, self.ccp1 = make_community_custom_page( + title="Test Title 1", + community=self.COMMUNITY_2, + content=self.content, + ) + self.p2, self.ccp2 = make_community_custom_page( + title="Test Title 2", + community=self.COMMUNITY_2, + content=self.content, + ) + + @staticmethod + def tearDownClass(): + pass + + def make_request(self, endpoint, data): + return self.client.post( + f"/api/{endpoint}", data=data, format="multipart" + ).json() + + def test_create_community_custom_page(self): + Console.header("Testing create custom page: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.create" + args = { + "title": "Test Title", + "community_id": self.COMMUNITY_1.id, + "content": [ + { + "options": {"position": 3}, + "block": { + "id": 1731409607781, + "name": "Paragraph", + "icon": "fa-paragraph", + "key": "paragraph", + "template": { + "element": { + "id": 1731409403366, + "type": "p", + "props": { + "style": {"padding": 10, "margin": 0}, + "text": "Piano scales are one of the first things you learn as a beginner piano player, but why are they so important? Well, playing your C major scale up and down isn’t just about practicing your technique; scales are a foundational musical concept. Understanding scales means you’ll understand key signatures and chords, which form the building blocks of Western music. In this post, we’ll discuss why scales are important, break down the different types of scales, and show you ways to apply these scales to your piano playing.", + }, + } + }, + }, + } + ], + } + + res = self.make_request(endpoint, args) + + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["page"]["title"], args["title"]) + self.assertIn("slug", res["data"]["page"]) + self.assertIn("content", res["data"]["page"]) + + Console.header("Testing create custom page: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing create custom page: with missing title") + args.pop("title") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Title") + + Console.header("Testing create custom page: with missing community_id") + args["title"] = "Test Title" + args.pop("community_id") + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Community Id") + + Console.header("Testing create custom page: with invalid community_id") + args["community_id"] = 000 + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "Community matching query does not exist.") + + Console.header("Testing create custom page: with audience and sharing_type") + args["community_id"] = self.COMMUNITY_1.id + args["audience"] = f"{self.COMMUNITY_2.id},{self.COMMUNITY_3.id}" + args["sharing_type"] = SharingType.OPEN_TO.value[0] + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["sharing_type"], args["sharing_type"]) + audience = [c["id"] for c in res["data"]["audience"]] + self.assertIn(self.COMMUNITY_2.id, audience) + self.assertIn(self.COMMUNITY_3.id, audience) + + def test_update_community_custom_page(self): + Console.header("Testing update custom page: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.update" + + args = { + "id": self.p1.id, + "title": "Test Title Updated", + "slug": f"{self.COMMUNITY_2.subdomain}-test-title-updated", + "content": [ + { + "options": {"position": 3}, + "block": { + "id": 1731409607781, + "name": "Paragraph", + "icon": "fa-paragraph", + "key": "paragraph", + "template": { + "element": { + "id": 1731409403366, + "type": "p", + "props": { + "style": {"padding": 10, "margin": 0}, + "text": "Piano scales are one of the first things you learn as a beginner piano player, but why are they so important? Well, playing your C major scale up and down isn’t just about practicing your technique; scales are a foundational musical concept. Understanding scales means you’ll understand key signatures and chords, which form the building blocks of Western music. In this post, we’ll discuss why scales are important, break down the different types of scales, and show you ways to apply these scales to your piano playing.", + }, + } + }, + }, + } + ], + } + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["page"]["title"], args["title"]) + self.assertEqual(res["data"]["page"]["slug"], args["slug"]) + + Console.header("Testing update custom page: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing update custom page: with audience and sharing_type") + args["audience"] = f"{self.COMMUNITY_1.id},{self.COMMUNITY_3.id}" + args["sharing_type"] = SharingType.CLOSED_TO.value[0] + + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["sharing_type"], args["sharing_type"]) + audience = [c["id"] for c in res["data"]["audience"]] + self.assertIn(self.COMMUNITY_1.id, audience) + self.assertIn(self.COMMUNITY_3.id, audience) + + Console.header("Testing update custom page: with missing id") + args.pop("id") + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Id") + + Console.header("Testing update custom page: with invalid id") + args["id"] = "e13a5038-3dea-45ef-9dd1-00e4148a987d" + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "CustomPage matching query does not exist.") + + def test_delete_community_custom_page(self): + Console.header("Testing delete custom page: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.delete" + + args = { + "id": self.p1.id, + } + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + + Console.header("Testing delete custom page: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing delete custom page: with missing id") + args.pop("id") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Id") + + Console.header("Testing delete custom page: with invalid id") + args["id"] = "invalid-id" + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "['“invalid-id” is not a valid UUID.']") + + def test_community_custom_page_info(self): + Console.header("Testing community custom page info: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.info" + + args = {"id": self.p1.id} + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["id"], str(self.p1.id)) + self.assertEqual(res["data"]["title"], self.p1.title) + + Console.header("Testing community custom page info: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + + Console.header("Testing community custom page info: with missing id") + args.pop("id") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Id") + + Console.header("Testing community custom page info: with invalid id") + args["id"] = "invalid-id" + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "['“invalid-id” is not a valid UUID.']") + + def test_share_community_custom_page(self): + Console.header("Testing share custom page: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.share" + + args = { + "community_page_id": self.ccp2.id, + "community_ids": f"{self.COMMUNITY_1.id},{self.COMMUNITY_3.id}", + } + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + shared_communities = [c["id"] for c in res["data"]["shared_with"]] + self.assertIn(self.COMMUNITY_1.id, shared_communities) + self.assertIn(self.COMMUNITY_3.id, shared_communities) + + Console.header("Testing share custom page: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing share custom page: with missing community_page_id") + args.pop("community_page_id") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual( + res["error"], "You are Missing a Required Input: Community Page Id" + ) + + Console.header("Testing share custom page: with missing community_ids") + args["community_page_id"] = self.p1.id + args.pop("community_ids") + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual( + res["error"], "You are Missing a Required Input: Community Ids" + ) + + Console.header("Testing share custom page: with invalid community_page_id") + args["community_page_id"] = "invalid-id" + args["community_ids"] = [self.COMMUNITY_1.id, self.COMMUNITY_3.id] + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "['“invalid-id” is not a valid UUID.']") + + Console.header("Testing share custom page: with invalid community_ids") + args["community_page_id"] = self.p1.id + args["community_ids"] = ["invalid-id"] + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "CommunityCustomPage matching query does not exist.") + + def test_publish_custom_page(self): + Console.header("Testing publish custom page: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "custom.page.publish" + + args = {"id": self.p1.id} + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertEqual(res["data"]["id"], str(self.p1.id)) + self.assertEqual(res["data"]["title"], self.p1.title) + self.assertIsNotNone(res["data"]["latest_version"]) + + Console.header("Testing publish custom page: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing publish custom page: with missing id") + args.pop("id") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Id") + + Console.header("Testing publish custom page: with invalid id") + args["id"] = "invalid-id" + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "['“invalid-id” is not a valid UUID.']") + + def test_list_community_custom_pages(self): + Console.header("Testing list community custom pages: as super admin") + + signinAs(self.client, self.SADMIN) + endpoint = "community.custom.pages.list" + + args = { + "community_id": self.COMMUNITY_1.id, + } + + res = self.make_request(endpoint, args) + self.assertTrue(res["success"]) + self.assertIsInstance(res["data"], list) + + Console.header("Testing list community custom pages: as user") + signinAs(self.client, self.user) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + + Console.header("Testing list community custom pages: with missing community_id") + args.pop("community_id") + signinAs(self.client, self.SADMIN) + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "You are Missing a Required Input: Community Id") + + Console.header("Testing list community custom pages: with invalid community_id") + args["community_id"] = "invalid-id" + res = self.make_request(endpoint, args) + self.assertFalse(res["success"]) + self.assertEqual(res["error"], "Field 'id' expected a number but got 'invalid-id'.") diff --git a/src/api/urls.py b/src/api/urls.py index 9d4da40c7..a3a91b235 100644 --- a/src/api/urls.py +++ b/src/api/urls.py @@ -4,6 +4,7 @@ from api.handlers.campaign import CampaignHandler from api.handlers.campaign_account import CampaignAccountHandler # from api.handlers.email_templates import EmailTemplatesHandler +from api.handlers.custom_pages import CustomPagesHandler from api.handlers.media_library import MediaLibraryHandler from api.handlers.page_settings__aboutus import AboutUsPageSettingsHandler from api.handlers.page_settings__actions import ActionsPageSettingsHandler @@ -55,6 +56,7 @@ CampaignHandler(), CampaignAccountHandler(), ContactUsPageSettingsHandler(), + CustomPagesHandler(), DeviceHandler(), DonatePageSettingsHandler(), DownloadHandler(), diff --git a/src/api/utils/api_utils.py b/src/api/utils/api_utils.py index aa35e0461..9190baae6 100644 --- a/src/api/utils/api_utils.py +++ b/src/api/utils/api_utils.py @@ -387,17 +387,15 @@ def create_unique_slug(title, model, field_name="slug", prefix=None): slug = slugify(title) if not model or not field_name: return slug.lower() if not prefix else f"{prefix}-{slug}".lower() + + slug = f"{prefix}-{slug}".lower() if prefix else slug.lower() if not model.objects.filter(**{field_name: slug}).exists(): - return slug.lower() if not prefix else f"{prefix}-{slug}".lower() - - if prefix: - prefixed_slug = f"{prefix}-{slug}".lower() - if not model.objects.filter(**{field_name: prefixed_slug}).exists(): - return prefixed_slug + return slug.lower() timestamp = str(int(datetime.now().timestamp())) - return f"{slug}-{timestamp}".lower() if not prefix else f"{prefix}-{slug}-{timestamp}".lower() + return f"{slug}-{timestamp}".lower() +