diff --git a/apps/commons/tests/test_pagination.py b/apps/commons/tests/test_pagination.py index b3e9ece1..cb0b7e6c 100644 --- a/apps/commons/tests/test_pagination.py +++ b/apps/commons/tests/test_pagination.py @@ -10,7 +10,7 @@ class PageInfoLimitOffsetPaginationTestCase(JwtAPITestCase): @classmethod def setUpTestData(cls): super().setUpTestData() - Organization.objects.bulk_create(OrganizationFactory.build_batch(5, faq=None)) + Organization.objects.bulk_create(OrganizationFactory.build_batch(5)) def test_first_page(self): pagination = PageInfoLimitOffsetPagination() diff --git a/apps/commons/tests/test_process_text.py b/apps/commons/tests/test_process_text.py index bbd278ab..581dfd6a 100644 --- a/apps/commons/tests/test_process_text.py +++ b/apps/commons/tests/test_process_text.py @@ -7,7 +7,6 @@ from apps.commons.test import JwtAPITestCase from apps.feedbacks.factories import CommentFactory from apps.organizations.factories import ( - FaqFactory, OrganizationFactory, ProjectCategoryFactory, TemplateFactory, @@ -180,54 +179,6 @@ def test_update_comment_content(self): content["content"], ) - def test_create_faq_content(self): - text = self.create_base64_image_text() + self.create_unlinked_image_text( - "Faq-images-detail", self.organization.code - ) - self.client.force_authenticate(self.user) - payload = { - "title": faker.sentence(), - "content": text, - "organization_code": self.organization.code, - } - response = self.client.post( - reverse("Faq-list", args=(self.organization.code,)), - data=payload, - ) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - content = response.json() - self.assertEqual(len(content["images"]), 2) - for image in content["images"]: - image_id = image["id"] - self.assertIn( - reverse("Faq-images-detail", args=(self.organization.code, image_id)), - content["content"], - ) - - def test_update_faq_content(self): - organization = OrganizationFactory() - text = self.create_base64_image_text() + self.create_unlinked_image_text( - "Faq-images-detail", organization.code - ) - self.client.force_authenticate(self.user) - FaqFactory(organization=organization) - payload = { - "content": text, - } - response = self.client.patch( - reverse("Faq-list", args=(organization.code,)), - data=payload, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - content = response.json() - self.assertEqual(len(content["images"]), 2) - for image in content["images"]: - image_id = image["id"] - self.assertIn( - reverse("Faq-images-detail", args=(organization.code, image_id)), - content["content"], - ) - def test_create_template_contents(self): text1 = self.create_base64_image_text() text2 = self.create_base64_image_text() diff --git a/apps/files/admin.py b/apps/files/admin.py index 4fb49fce..23c96c3e 100644 --- a/apps/files/admin.py +++ b/apps/files/admin.py @@ -16,7 +16,6 @@ class ImageUploadToChoices(models.TextChoices): ORGANIZATION_LOGO = "organization/logo/" ORGANIZATION_BANNER = "organization/banner/" ORGANIZATION_IMAGES = "organization/images/" - FAQ_IMAGES = "faq/images/" CATEGORY_BACKGROUND = "category/background/" TEMPLATE_IMAGES = "template/images/" EMAIL_IMAGES = "email/images/" diff --git a/apps/files/management/commands/link_images.py b/apps/files/management/commands/link_images.py index 9b426180..30d0b307 100644 --- a/apps/files/management/commands/link_images.py +++ b/apps/files/management/commands/link_images.py @@ -2,7 +2,7 @@ from apps.commons.utils import process_unlinked_images from apps.feedbacks.models import Comment -from apps.organizations.models import Faq, Template +from apps.organizations.models import Template from apps.projects.models import BlogEntry, Project @@ -24,9 +24,6 @@ def handle(self, *args, **options): ) images2 = process_unlinked_images(instance, instance.blogentry_placeholder) instance.images.add(*(images1 + images2)) - for instance in Faq.objects.all(): - images = process_unlinked_images(instance, instance.content) - instance.images.add(*images) for instance in Comment.objects.all(): updated_at = instance.updated_at images = process_unlinked_images(instance, instance.content) diff --git a/apps/files/models.py b/apps/files/models.py index d095fcf8..0045ac7e 100644 --- a/apps/files/models.py +++ b/apps/files/models.py @@ -244,8 +244,6 @@ def get_related_organizations(self) -> List["Organization"]: return [self.organization_banner.get()] if self.organizations.exists(): return [self.organizations.get()] - if self.faqs.exists(): - return [self.faqs.get().organization] if self.project_category.exists(): return [self.project_category.get().organization] if self.project_header.exists(): diff --git a/apps/organizations/admin.py b/apps/organizations/admin.py index aeff4c99..66c52dab 100644 --- a/apps/organizations/admin.py +++ b/apps/organizations/admin.py @@ -16,7 +16,6 @@ class OrganizationAdmin(admin.ModelAdmin): readonly_fields = ( "groups", "images", - "faq", ) search_fields = ( "code", diff --git a/apps/organizations/factories.py b/apps/organizations/factories.py index 9ef8d064..4bb810e6 100644 --- a/apps/organizations/factories.py +++ b/apps/organizations/factories.py @@ -5,7 +5,7 @@ from apps.accounts.factories import UserFactory from apps.commons.factories import language_factory from apps.commons.utils import get_test_image -from apps.organizations.models import Faq, Organization, ProjectCategory, Template +from apps.organizations.models import Organization, ProjectCategory, Template faker = Faker() @@ -44,24 +44,6 @@ def with_admin(self, create, extracted, **kwargs): UserFactory(groups=[self.get_admins()]) -class FaqFactory(factory.django.DjangoModelFactory): - class Meta: - model = Faq - - title = factory.Faker("text", max_nb_chars=50) - content = factory.Faker("text") - organization = factory.LazyFunction( - lambda: OrganizationFactory() - ) # Subfactory seems to not trigger `create()` - - @classmethod - def create(cls, **kwargs): - instance = super().create(**kwargs) - instance.organization.faq = instance - instance.organization.save() - return instance - - class TemplateFactory(factory.django.DjangoModelFactory): class Meta: model = Template diff --git a/apps/organizations/migrations/0019_alter_organization_options_remove_organization_faq_and_more.py b/apps/organizations/migrations/0019_alter_organization_options_remove_organization_faq_and_more.py new file mode 100644 index 00000000..ae28a251 --- /dev/null +++ b/apps/organizations/migrations/0019_alter_organization_options_remove_organization_faq_and_more.py @@ -0,0 +1,85 @@ +# Generated by Django 4.2.18 on 2025-02-07 14:31 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("organizations", "0018_remove_organization_wikipedia_tags_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="organization", + options={ + "permissions": ( + ("access_admin", "Can access the admin panel"), + ("view_stat", "Can view stats"), + ("view_org_project", "Can view community projects"), + ("view_org_projectuser", "Can view community users"), + ("view_org_peoplegroup", "Can view community groups"), + ("lock_project", "Can lock and unlock a project"), + ("duplicate_project", "Can duplicate a project"), + ("change_locked_project", "Can update a locked project"), + ("manage_accessrequest", "Can manage access requests"), + ("view_project", "Can view projects"), + ("add_project", "Can add projects"), + ("change_project", "Can change projects"), + ("delete_project", "Can delete projects"), + ("view_projectmessage", "Can view project messages"), + ("add_projectmessage", "Can add project messages"), + ("change_projectmessage", "Can change project messages"), + ("delete_projectmessage", "Can delete project messages"), + ("view_projectuser", "Can view users"), + ("add_projectuser", "Can add users"), + ("change_projectuser", "Can change users"), + ("delete_projectuser", "Can delete users"), + ("view_peoplegroup", "Can view groups"), + ("add_peoplegroup", "Can add groups"), + ("change_peoplegroup", "Can change groups"), + ("delete_peoplegroup", "Can delete groups"), + ("view_news", "Can view news"), + ("add_news", "Can add news"), + ("change_news", "Can change news"), + ("delete_news", "Can delete news"), + ("view_event", "Can view event"), + ("add_event", "Can add event"), + ("change_event", "Can change event"), + ("delete_event", "Can delete event"), + ("view_instruction", "Can view instructions"), + ("add_instruction", "Can add instructions"), + ("change_instruction", "Can change instructions"), + ("delete_instruction", "Can delete instructions"), + ("add_tag", "Can add tags"), + ("change_tag", "Can change tags"), + ("delete_tag", "Can delete tags"), + ("add_tagclassification", "Can add tag classifications"), + ("change_tagclassification", "Can change tag classifications"), + ("delete_tagclassification", "Can delete tag classifications"), + ("add_projectcategory", "Can add project categories"), + ("change_projectcategory", "Can change project categories"), + ("delete_projectcategory", "Can delete project categories"), + ("add_review", "Can add reviews"), + ("change_review", "Can change reviews"), + ("delete_review", "Can delete reviews"), + ("add_comment", "Can add comments"), + ("change_comment", "Can change comments"), + ("delete_comment", "Can delete comments"), + ("add_follow", "Can add follows"), + ("change_follow", "Can change follows"), + ("delete_follow", "Can delete follows"), + ("add_invitation", "Can add invitation links"), + ("change_invitation", "Can change invitation links"), + ("delete_invitation", "Can delete invitation links"), + ) + }, + ), + migrations.RemoveField( + model_name="organization", + name="faq", + ), + migrations.DeleteModel( + name="Faq", + ), + ] diff --git a/apps/organizations/models.py b/apps/organizations/models.py index 9c7c2de3..3af4bd57 100644 --- a/apps/organizations/models.py +++ b/apps/organizations/models.py @@ -19,26 +19,6 @@ from apps.accounts.models import ProjectUser -class Faq(models.Model, OrganizationRelated): - """Frequently asked question of an organization. - - title: CharField - Name of the FAQ. - content: TextField - Content of the FAQ. - images: ManyToManyField - Images used by the FAQ. - """ - - title = models.CharField(max_length=255) - images = models.ManyToManyField("files.Image", related_name="faqs") - content = models.TextField(blank=True) - - def get_related_organizations(self) -> List["Organization"]: - """Return the organization related to this model.""" - return [self.organization] - - class Organization(PermissionsSetupModel, OrganizationRelated): """An Organization is a set of ProjectCategories contained in an OrganizationDirectory. @@ -70,8 +50,6 @@ class Organization(PermissionsSetupModel, OrganizationRelated): Main language of the organization. website_url: CharField Organization's website. - faq: OneToOneField, optional - The organization's frequently asked questions. is_logo_visible_on_parent_dashboard: BooleanField Whether to show or hide the organization's logo on the main organization's portal. @@ -145,9 +123,6 @@ class DefaultGroup(models.TextChoices): "files.Image", related_name="organizations", blank=True ) - faq = models.OneToOneField( - Faq, on_delete=models.SET_NULL, null=True, related_name="organization" - ) identity_providers = models.ManyToManyField( "keycloak.IdentityProvider", related_name="organizations", blank=True ) @@ -207,7 +182,6 @@ class Meta: write_only_subscopes = ( ("tag", "tags"), ("tagclassification", "tag classifications"), - ("faq", "faqs"), ("projectcategory", "project categories"), ("review", "reviews"), ("comment", "comments"), @@ -264,7 +238,6 @@ def get_default_facilitators_permissions(self) -> QuerySet[Permission]: for subscope in [ "tag", "review", - "faq", "projectcategory", "tagclassification", ] diff --git a/apps/organizations/serializers.py b/apps/organizations/serializers.py index cde50a57..289b0819 100644 --- a/apps/organizations/serializers.py +++ b/apps/organizations/serializers.py @@ -33,66 +33,11 @@ ParentCategoryOrganizationError, RootCategoryParentError, ) -from .models import Faq, Organization, ProjectCategory, Template +from .models import Organization, ProjectCategory, Template logger = logging.getLogger(__name__) -class FaqSerializer(OrganizationRelatedSerializer): - images = ImageSerializer(many=True, read_only=True) - organization_code = serializers.SlugRelatedField( - write_only=True, - slug_field="code", - source="organization", - queryset=Organization.objects.all(), - ) - images_ids = serializers.PrimaryKeyRelatedField( - many=True, - write_only=True, - queryset=Image.objects.all(), - source="images", - required=False, - ) - - class Meta: - model = Faq - fields = [ - "id", - "title", - "content", - # read only - "images", - # write only - "organization_code", - "images_ids", - ] - - def get_related_organizations(self) -> List[Organization]: - """Retrieve the related organizations""" - if "organization" in self.validated_data: - return [self.validated_data["organization"]] - return [] - - @transaction.atomic - def save(self, **kwargs): - if "content" in self.validated_data: - if not self.instance: - super(FaqSerializer, self).save(**kwargs) - text, images = process_text( - request=self.context["request"], - instance=self.instance, - text=self.validated_data["content"], - upload_to="faq/images/", - view="Faq-images-detail", - organization_code=self.instance.organization.code, - ) - self.validated_data["content"] = text - self.validated_data["images"] = images + [ - image for image in self.instance.images.all() - ] - return super(FaqSerializer, self).save(**kwargs) - - class OrganizationAddTeamMembersSerializer(serializers.Serializer): organization = HiddenPrimaryKeyRelatedField( required=False, write_only=True, queryset=Organization.objects.all() @@ -201,7 +146,6 @@ class OrganizationSerializer(OrganizationRelatedSerializer): # read_only banner_image = ImageSerializer(read_only=True) logo_image = ImageSerializer(read_only=True) - faq = FaqSerializer(many=False, read_only=True) identity_providers = IdentityProviderSerializer(many=True, read_only=True) google_sync_enabled = serializers.SerializerMethodField() children = SlugRelatedField( @@ -252,7 +196,6 @@ class Meta: # read_only "banner_image", "logo_image", - "faq", "children", "google_sync_enabled", "identity_providers", diff --git a/apps/organizations/signals.py b/apps/organizations/signals.py index 00bdab65..f45611e5 100644 --- a/apps/organizations/signals.py +++ b/apps/organizations/signals.py @@ -1,25 +1,8 @@ -from django.db.models.signals import post_save, pre_delete +from django.db.models.signals import pre_delete from django.dispatch import receiver -from .models import Faq - @receiver(pre_delete, sender="organizations.Organization") def delete_organization_roles(sender, instance, **kwargs): """Delete the associated roles.""" instance.groups.all().delete() - - -@receiver(post_save, sender="organizations.Organization") -def create_organization_faq(sender, instance, created, **kwargs): - """Create a FAQ for the organization.""" - if instance.faq is None: - instance.faq = Faq.objects.create(title="", content="") - instance.save() - - -@receiver(pre_delete, sender="organizations.Faq") -def create_new_organization_faq(sender, instance, **kwargs): - """Create a new FAQ for the organization.""" - instance.organization.faq = Faq.objects.create(title="", content="") - instance.organization.save() diff --git a/apps/organizations/tests/views/test_faq.py b/apps/organizations/tests/views/test_faq.py deleted file mode 100644 index a6e491e3..00000000 --- a/apps/organizations/tests/views/test_faq.py +++ /dev/null @@ -1,139 +0,0 @@ -from django.urls import reverse -from faker import Faker -from parameterized import parameterized -from rest_framework import status - -from apps.commons.test import JwtAPITestCase, TestRoles -from apps.organizations.factories import FaqFactory, OrganizationFactory -from apps.organizations.models import Faq - -faker = Faker() - - -class CreateFaqTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.organization = OrganizationFactory() - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_201_CREATED), - (TestRoles.ORG_ADMIN, status.HTTP_201_CREATED), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_create_faq(self, role, expected_code): - user = self.get_parameterized_test_user(role, instances=[self.organization]) - self.client.force_authenticate(user) - payload = { - "title": faker.sentence(), - "content": faker.text(), - "organization_code": self.organization.code, - } - response = self.client.post( - reverse("Faq-list", args=(self.organization.code,)), - data=payload, - ) - self.assertEqual(response.status_code, expected_code) - if response.status_code == status.HTTP_201_CREATED: - content = response.json() - self.assertEqual(content["title"], payload["title"]) - self.assertEqual(content["content"], payload["content"]) - - -class RetrieveFaqTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.organization = OrganizationFactory() - cls.faq = FaqFactory(organization=cls.organization) - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS,), - (TestRoles.DEFAULT,), - ] - ) - def test_retrieve_faq(self, role): - user = self.get_parameterized_test_user(role, instances=[self.organization]) - self.client.force_authenticate(user) - response = self.client.get( - reverse("Faq-list", args=(self.organization.code,)), - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - content = response.json() - self.assertEqual(content["title"], self.organization.faq.title) - self.assertEqual(content["content"], self.organization.faq.content) - - -class UpdateFaqTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.organization = OrganizationFactory() - cls.faq = FaqFactory(organization=cls.organization) - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_200_OK), - (TestRoles.ORG_ADMIN, status.HTTP_200_OK), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_update_faq(self, role, expected_code): - user = self.get_parameterized_test_user(role, instances=[self.organization]) - self.client.force_authenticate(user) - payload = { - "title": faker.sentence(), - "content": faker.text(), - } - response = self.client.patch( - reverse("Faq-list", args=(self.organization.code,)), - data=payload, - ) - self.assertEqual(response.status_code, expected_code) - if response.status_code == status.HTTP_200_OK: - content = response.json() - self.assertEqual(content["title"], payload["title"]) - self.assertEqual(content["content"], payload["content"]) - - -class DeleteFaqTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.organization = OrganizationFactory() - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_204_NO_CONTENT), - (TestRoles.ORG_ADMIN, status.HTTP_204_NO_CONTENT), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_delete_faq(self, role, expected_code): - faq = FaqFactory(organization=self.organization) - user = self.get_parameterized_test_user(role, instances=[self.organization]) - self.client.force_authenticate(user) - response = self.client.delete( - reverse("Faq-list", args=(self.organization.code,)), - ) - self.assertEqual(response.status_code, expected_code) - if response.status_code == status.HTTP_204_NO_CONTENT: - self.assertFalse(Faq.objects.filter(id=faq.id).exists()) - new_faq = Faq.objects.filter(organization=self.organization) - self.assertTrue(new_faq.exists()) - new_faq = new_faq.get() - self.assertEqual(new_faq.title, "") - self.assertEqual(new_faq.content, "") - self.assertEqual(new_faq.images.count(), 0) diff --git a/apps/organizations/tests/views/test_faq_images.py b/apps/organizations/tests/views/test_faq_images.py deleted file mode 100644 index f2637d95..00000000 --- a/apps/organizations/tests/views/test_faq_images.py +++ /dev/null @@ -1,160 +0,0 @@ -from django.urls import reverse -from faker import Faker -from parameterized import parameterized -from rest_framework import status - -from apps.accounts.factories import UserFactory -from apps.commons.test import JwtAPITestCase, TestRoles -from apps.files.models import Image -from apps.organizations.factories import FaqFactory - -faker = Faker() - - -class RetrieveFaqImageTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.faq = FaqFactory() - cls.image = cls.get_test_image() - cls.faq.images.add(cls.image) - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS,), - (TestRoles.DEFAULT,), - ] - ) - def test_retrieve_faq_image(self, role): - user = self.get_parameterized_test_user(role, instances=[]) - self.client.force_authenticate(user) - response = self.client.get( - reverse( - "Faq-images-detail", args=(self.faq.organization.code, self.image.id) - ) - ) - self.assertEqual(response.status_code, status.HTTP_302_FOUND) - - -class CreateFaqImageTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.faq = FaqFactory() - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_201_CREATED), - (TestRoles.ORG_ADMIN, status.HTTP_201_CREATED), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_create_faq_image(self, role, expected_code): - user = self.get_parameterized_test_user(role, instances=[self.faq.organization]) - self.client.force_authenticate(user) - payload = {"file": self.get_test_image_file()} - response = self.client.post( - reverse("Faq-images-list", args=(self.faq.organization.code,)), - data=payload, - format="multipart", - ) - self.assertEqual(response.status_code, expected_code) - if expected_code == status.HTTP_201_CREATED: - content = response.json() - self.assertIsNotNone(content["static_url"]) - self.assertEqual( - content["static_url"] + "/", - reverse( - "Faq-images-detail", - args=(self.faq.organization.code, content["id"]), - ), - ) - - -class UpdateFaqImageTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.faq = FaqFactory() - cls.owner = UserFactory() - cls.image = cls.get_test_image(owner=cls.owner) - cls.faq.images.add(cls.image) - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.OWNER, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_200_OK), - (TestRoles.ORG_ADMIN, status.HTTP_200_OK), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_update_faq_image(self, role, expected_code): - user = self.get_parameterized_test_user( - role, instances=[self.faq.organization], owned_instance=self.image - ) - self.client.force_authenticate(user) - payload = { - "scale_x": faker.pyfloat(min_value=1.0, max_value=2.0), - "scale_y": faker.pyfloat(min_value=1.0, max_value=2.0), - "left": faker.pyfloat(min_value=1.0, max_value=2.0), - "top": faker.pyfloat(min_value=1.0, max_value=2.0), - "natural_ratio": faker.pyfloat(min_value=1.0, max_value=2.0), - } - response = self.client.patch( - reverse( - "Faq-images-detail", - args=(self.faq.organization.code, self.image.id), - ), - data=payload, - format="multipart", - ) - self.assertEqual(response.status_code, expected_code) - if expected_code == status.HTTP_200_OK: - content = response.json() - self.assertEqual(content["scale_x"], payload["scale_x"]) - self.assertEqual(content["scale_y"], payload["scale_y"]) - self.assertEqual(content["left"], payload["left"]) - self.assertEqual(content["top"], payload["top"]) - self.assertEqual(content["natural_ratio"], payload["natural_ratio"]) - - -class DeleteFaqImageTestCase(JwtAPITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - cls.faq = FaqFactory() - cls.owner = UserFactory() - - @parameterized.expand( - [ - (TestRoles.ANONYMOUS, status.HTTP_401_UNAUTHORIZED), - (TestRoles.DEFAULT, status.HTTP_403_FORBIDDEN), - (TestRoles.OWNER, status.HTTP_403_FORBIDDEN), - (TestRoles.SUPERADMIN, status.HTTP_204_NO_CONTENT), - (TestRoles.ORG_ADMIN, status.HTTP_204_NO_CONTENT), - (TestRoles.ORG_FACILITATOR, status.HTTP_403_FORBIDDEN), - (TestRoles.ORG_USER, status.HTTP_403_FORBIDDEN), - ] - ) - def test_delete_faq_image(self, role, expected_code): - image = self.get_test_image(owner=self.owner) - self.faq.images.add(image) - user = self.get_parameterized_test_user( - role, instances=[self.faq.organization], owned_instance=image - ) - self.client.force_authenticate(user) - response = self.client.delete( - reverse( - "Faq-images-detail", - args=(self.faq.organization.code, image.id), - ), - ) - self.assertEqual(response.status_code, expected_code) - if expected_code == status.HTTP_204_NO_CONTENT: - self.assertFalse(Image.objects.filter(id=image.id).exists()) diff --git a/apps/organizations/urls.py b/apps/organizations/urls.py index 36241861..088d31bf 100644 --- a/apps/organizations/urls.py +++ b/apps/organizations/urls.py @@ -33,9 +33,6 @@ organizations_nested_router = routers.NestedSimpleRouter( organizations_router, r"organization", lookup="organization" ) -organizations_nested_router.register( - r"faq-image", views.FaqImagesView, basename="Faq-images" -) organizations_nested_router.register( r"banner", views.OrganizationBannerView, basename="Organization-banner" ) @@ -58,9 +55,6 @@ details_only_organizations_nested_router = DetailOnlyNestedRouter( organizations_router, r"organization", lookup="organization" ) -details_only_organizations_nested_router.register( - r"faq", views.FaqViewSet, basename="Faq" -) details_only_people_groups_nested_router = DetailOnlyNestedRouter( organizations_nested_router, r"people-group", lookup="people_group" diff --git a/apps/organizations/views.py b/apps/organizations/views.py index d5a4d439..f736532d 100644 --- a/apps/organizations/views.py +++ b/apps/organizations/views.py @@ -2,7 +2,7 @@ from django.conf import settings from django.db import transaction -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import redirect from django.utils.decorators import method_decorator from django_filters.rest_framework import DjangoFilterBackend from drf_spectacular.utils import OpenApiParameter, extend_schema @@ -20,10 +20,9 @@ from apps.files.models import Image from apps.files.views import ImageStorageView from apps.organizations.filters import OrganizationFilter, ProjectCategoryFilter -from apps.organizations.models import Faq, Organization, ProjectCategory +from apps.organizations.models import Organization, ProjectCategory from apps.organizations.permissions import HasOrganizationPermission from apps.organizations.serializers import ( - FaqSerializer, OrganizationAddFeaturedProjectsSerializer, OrganizationAddTeamMembersSerializer, OrganizationLightSerializer, @@ -108,39 +107,6 @@ def add_image_to_model(self, image): return None -class FaqImagesView(ImageStorageView): - permission_classes = [ - IsAuthenticatedOrReadOnly, - ReadOnly - | HasBasePermission("change_faq", "organizations") - | HasOrganizationPermission("change_faq"), - ] - - def get_queryset(self): - if "organization_code" in self.kwargs: - qs = Image.objects.filter( - faqs__organization__code=self.kwargs["organization_code"] - ) - # Retrieve images before the faq is posted - if self.request.user.is_authenticated: - qs = qs | Image.objects.filter(owner=self.request.user) - return qs.distinct() - return Image.objects.none() - - @staticmethod - def upload_to(instance, filename) -> str: - return f"faq/images/{uuid.uuid4()}#{instance.name}" - - def retrieve(self, request, *args, **kwargs): - image = self.get_object() - return redirect(image.file.url) - - def add_image_to_model(self, image, *args, **kwargs): - if "organization_code" in self.kwargs: - return f"/v1/organization/{self.kwargs['organization_code']}/faq-image/{image.id}" - return None - - class OrganizationViewSet(viewsets.ModelViewSet): serializer_class = OrganizationSerializer filter_backends = [DjangoFilterBackend] @@ -343,51 +309,6 @@ def featured_project(self, request, *args, **kwargs): return Response(project_serializer.data) -class FaqViewSet(viewsets.ModelViewSet): - serializer_class = FaqSerializer - lookup_field = "id" - lookup_value_regex = "[0-9]+" - - def get_permissions(self): - codename = map_action_to_permission(self.action, "faq") - if codename: - self.permission_classes = [ - IsAuthenticatedOrReadOnly, - ReadOnly - | HasBasePermission(codename, "organizations") - | HasOrganizationPermission(codename), - ] - return super().get_permissions() - - def get_queryset(self): - if "organization_code" in self.kwargs: - return Faq.objects.filter( - organization__code=self.kwargs["organization_code"] - ) - return Faq.objects.none() - - def get_object(self): - """ - Retrieve the object within the QuerySet. - - There should be only one Faq in the QuerySet since we filter by - `organization_code` in `get_queryset` and there is a one to one relation - between the objects. - """ - queryset = self.filter_queryset(self.get_queryset()) - # No need to give additional filters since there should be only one - # object in the QuerySet - obj = get_object_or_404(queryset) - # May raise a permission denied - self.check_object_permissions(self.request, obj) - return obj - - def perform_create(self, serializer): - organization = serializer.validated_data["organization"] - organization.faq = serializer.save() - organization.save() - - class OrganizationBannerView(ImageStorageView): permission_classes = [ IsAuthenticatedOrReadOnly,