From 43f70eae660000435f7c8adfd19c2244e5076d78 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 11:29:39 -0300 Subject: [PATCH 1/9] Move serializers to proper file in sponsors app --- sponsors/api.py | 45 +---------------------------------------- sponsors/serializers.py | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 44 deletions(-) create mode 100644 sponsors/serializers.py diff --git a/sponsors/api.py b/sponsors/api.py index 7b99d0e91..005545ea4 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -2,53 +2,10 @@ from django.urls import reverse from rest_framework import permissions -from rest_framework import serializers -from rest_framework.authentication import TokenAuthentication from rest_framework.views import APIView from rest_framework.response import Response from sponsors.models import BenefitFeature, LogoPlacement, Sponsorship -from sponsors.models.enums import PublisherChoices, LogoPlacementChoices - - -class LogoPlacementSerializer(serializers.Serializer): - publisher = serializers.CharField() - flight = serializers.CharField() - sponsor = serializers.CharField() - sponsor_slug = serializers.CharField() - description = serializers.CharField() - logo = serializers.URLField() - start_date = serializers.DateField() - end_date = serializers.DateField() - sponsor_url = serializers.URLField() - level_name = serializers.CharField() - level_order = serializers.IntegerField() - - -class FilterLogoPlacementsSerializer(serializers.Serializer): - publisher = serializers.ChoiceField( - choices=[(c.value, c.name.replace("_", " ").title()) for c in PublisherChoices], - required=False, - ) - flight = serializers.ChoiceField( - choices=[(c.value, c.name.replace("_", " ").title()) for c in LogoPlacementChoices], - required=False, - ) - - @property - def by_publisher(self): - return self.validated_data.get("publisher") - - @property - def by_flight(self): - return self.validated_data.get("flight") - - def skip_logo(self, logo): - if self.by_publisher and self.by_publisher != logo.publisher: - return True - if self.by_flight and self.by_flight != logo.logo_place: - return True - else: - return False +from sponsors.serializers import LogoPlacementSerializer, FilterLogoPlacementsSerializer class SponsorPublisherPermission(permissions.BasePermission): diff --git a/sponsors/serializers.py b/sponsors/serializers.py new file mode 100644 index 000000000..742fe18b3 --- /dev/null +++ b/sponsors/serializers.py @@ -0,0 +1,43 @@ + +from rest_framework import serializers +from sponsors.models.enums import PublisherChoices, LogoPlacementChoices + +class LogoPlacementSerializer(serializers.Serializer): + publisher = serializers.CharField() + flight = serializers.CharField() + sponsor = serializers.CharField() + sponsor_slug = serializers.CharField() + description = serializers.CharField() + logo = serializers.URLField() + start_date = serializers.DateField() + end_date = serializers.DateField() + sponsor_url = serializers.URLField() + level_name = serializers.CharField() + level_order = serializers.IntegerField() + + +class FilterLogoPlacementsSerializer(serializers.Serializer): + publisher = serializers.ChoiceField( + choices=[(c.value, c.name.replace("_", " ").title()) for c in PublisherChoices], + required=False, + ) + flight = serializers.ChoiceField( + choices=[(c.value, c.name.replace("_", " ").title()) for c in LogoPlacementChoices], + required=False, + ) + + @property + def by_publisher(self): + return self.validated_data.get("publisher") + + @property + def by_flight(self): + return self.validated_data.get("flight") + + def skip_logo(self, logo): + if self.by_publisher and self.by_publisher != logo.publisher: + return True + if self.by_flight and self.by_flight != logo.logo_place: + return True + else: + return False From 265413a1f60406dd17204a6f6f57478acc38ff35 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 11:54:57 -0300 Subject: [PATCH 2/9] Minimal config to have the endpoint set up --- pydotorg/urls_api.py | 3 ++- sponsors/api.py | 7 +++++++ sponsors/tests/test_api.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/pydotorg/urls_api.py b/pydotorg/urls_api.py index 688a3dee5..0c27699b1 100644 --- a/pydotorg/urls_api.py +++ b/pydotorg/urls_api.py @@ -7,7 +7,7 @@ from downloads.api import OSViewSet, ReleaseViewSet, ReleaseFileViewSet from pages.api import PageResource from pages.api import PageViewSet -from sponsors.api import LogoPlacementeAPIList +from sponsors.api import LogoPlacementeAPIList, SponsorshipAssetsAPIList v1_api = Api(api_name='v1') v1_api.register(PageResource()) @@ -23,4 +23,5 @@ urlpatterns = [ url(r'sponsors/logo-placement/', LogoPlacementeAPIList.as_view(), name="logo_placement_list"), + url(r'sponsors/sponsorship-assets/', SponsorshipAssetsAPIList.as_view(), name="assets_list"), ] diff --git a/sponsors/api.py b/sponsors/api.py index 005545ea4..062466d5d 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -57,3 +57,10 @@ def get(self, request, *args, **kwargs): serializer = LogoPlacementSerializer(placements, many=True) return Response(serializer.data) + + +class SponsorshipAssetsAPIList(APIView): + permission_classes = [SponsorPublisherPermission] + + def get(self, request, *args, **kwargs): + return Response("ok") diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index ab0b7d15c..eb58ce898 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -129,3 +129,39 @@ def test_bad_request_for_invalid_filters(self): self.assertEqual(400, response.status_code) self.assertIn("flight", data) self.assertIn("publisher", data) + + +class SponsorshipAssetsAPIListTests(APITestCase): + url = reverse_lazy("assets_list") + + def setUp(self): + self.user = baker.make('users.User') + token = Token.objects.get(user=self.user) + self.permission = Permission.objects.get(name='Can access sponsor placement API') + self.user.user_permissions.add(self.permission) + self.authorization = f'Token {token.key}' + + def test_invalid_token(self): + Token.objects.all().delete() + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + self.assertEqual(401, response.status_code) + + def test_superuser_user_have_permission_by_default(self): + self.user.user_permissions.remove(self.permission) + self.user.is_superuser = True + self.user.is_staff = True + self.user.save() + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + self.assertEqual(200, response.status_code) + + def test_staff_have_permission_by_default(self): + self.user.user_permissions.remove(self.permission) + self.user.is_staff = True + self.user.save() + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + self.assertEqual(200, response.status_code) + + def test_user_must_have_required_permission(self): + self.user.user_permissions.remove(self.permission) + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + self.assertEqual(403, response.status_code) From ec3e27536b3fd344db27f08e53650d3d5206d188 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 12:07:12 -0300 Subject: [PATCH 3/9] Introduce custom query set on generic assets to list all specific implementations --- sponsors/admin.py | 3 +-- sponsors/models/assets.py | 4 +++- sponsors/models/managers.py | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/sponsors/admin.py b/sponsors/admin.py index c2b471e7c..ff92aabac 100644 --- a/sponsors/admin.py +++ b/sponsors/admin.py @@ -828,8 +828,7 @@ def get_child_models(self, *args, **kwargs): return GenericAsset.all_asset_types() def get_queryset(self, *args, **kwargs): - classes = self.get_child_models(*args, **kwargs) - return self.model.objects.select_related("content_type").instance_of(*classes) + return GenericAsset.objects.all_assets() def get_actions(self, request): actions = super().get_actions(request) diff --git a/sponsors/models/assets.py b/sponsors/models/assets.py index e0762188c..4db7c9671 100644 --- a/sponsors/models/assets.py +++ b/sponsors/models/assets.py @@ -13,6 +13,8 @@ from polymorphic.managers import PolymorphicManager from polymorphic.models import PolymorphicModel +from sponsors.models.managers import GenericAssetQuerySet + def generic_asset_path(instance, filename): """ @@ -28,7 +30,7 @@ class GenericAsset(PolymorphicModel): """ Base class used to add required assets to Sponsor or Sponsorship objects """ - objects = PolymorphicManager() + objects = GenericAssetQuerySet.as_manager() non_polymorphic = models.Manager() # UUID can't be the object ID because Polymorphic expects default django integer ID diff --git a/sponsors/models/managers.py b/sponsors/models/managers.py index cc8657aea..3c06f5794 100644 --- a/sponsors/models/managers.py +++ b/sponsors/models/managers.py @@ -118,3 +118,11 @@ def provided_assets(self): from sponsors.models.benefits import ProvidedAssetMixin provided_assets_classes = ProvidedAssetMixin.__subclasses__() return self.instance_of(*provided_assets_classes).select_related("sponsor_benefit__sponsorship") + + +class GenericAssetQuerySet(PolymorphicQuerySet): + + def all_assets(self): + from sponsors.models import GenericAsset + classes = GenericAsset.all_asset_types() + return self.select_related("content_type").instance_of(*classes) From 376a39f3554ad3571bbe04fb505b38621987e30f Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 12:14:56 -0300 Subject: [PATCH 4/9] Refactor to rely on DRF default behavior to serialize bad requests --- sponsors/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sponsors/api.py b/sponsors/api.py index 062466d5d..cee89dd2b 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -25,8 +25,7 @@ class LogoPlacementeAPIList(APIView): def get(self, request, *args, **kwargs): placements = [] logo_filters = FilterLogoPlacementsSerializer(data=request.GET) - if not logo_filters.is_valid(): - return Response(logo_filters.errors, status=400) + logo_filters.is_valid(raise_exception=True) sponsorships = Sponsorship.objects.enabled().with_logo_placement() for sponsorship in sponsorships.select_related("sponsor").iterator(): From 520af9ba0fc63febd07e89a8fa20136088ac5d66 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 12:43:04 -0300 Subject: [PATCH 5/9] Serialize text assets --- sponsors/api.py | 15 +++++++++--- sponsors/serializers.py | 26 ++++++++++++++++++++ sponsors/tests/test_api.py | 49 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/sponsors/api.py b/sponsors/api.py index cee89dd2b..1466a74cc 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -4,8 +4,9 @@ from rest_framework import permissions from rest_framework.views import APIView from rest_framework.response import Response -from sponsors.models import BenefitFeature, LogoPlacement, Sponsorship -from sponsors.serializers import LogoPlacementSerializer, FilterLogoPlacementsSerializer +from sponsors.models import BenefitFeature, LogoPlacement, Sponsorship, GenericAsset +from sponsors.serializers import LogoPlacementSerializer, FilterLogoPlacementsSerializer, FilterAssetsSerializer, \ + AssetSerializer class SponsorPublisherPermission(permissions.BasePermission): @@ -62,4 +63,12 @@ class SponsorshipAssetsAPIList(APIView): permission_classes = [SponsorPublisherPermission] def get(self, request, *args, **kwargs): - return Response("ok") + assets_filter = FilterAssetsSerializer(data=request.GET) + assets_filter.is_valid(raise_exception=True) + + assets = GenericAsset.objects.all_assets().filter( + internal_name=assets_filter.by_internal_name).iterator() + assets = (a for a in assets if a.has_value or assets_filter.accept_empty) + serializer = AssetSerializer(assets, many=True) + + return Response(serializer.data) diff --git a/sponsors/serializers.py b/sponsors/serializers.py index 742fe18b3..59e379ca4 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -1,5 +1,7 @@ from rest_framework import serializers + +from sponsors.models import GenericAsset from sponsors.models.enums import PublisherChoices, LogoPlacementChoices class LogoPlacementSerializer(serializers.Serializer): @@ -16,6 +18,17 @@ class LogoPlacementSerializer(serializers.Serializer): level_order = serializers.IntegerField() +class AssetSerializer(serializers.ModelSerializer): + content_type = serializers.SerializerMethodField() + + class Meta: + model = GenericAsset + fields = ["internal_name", "uuid", "value", "content_type"] + + def get_content_type(self, asset): + return asset.content_type.name.title() + + class FilterLogoPlacementsSerializer(serializers.Serializer): publisher = serializers.ChoiceField( choices=[(c.value, c.name.replace("_", " ").title()) for c in PublisherChoices], @@ -41,3 +54,16 @@ def skip_logo(self, logo): return True else: return False + + +class FilterAssetsSerializer(serializers.Serializer): + internal_name = serializers.CharField(max_length=128) + list_empty = serializers.BooleanField(required=False, default=False) + + @property + def by_internal_name(self): + return self.validated_data["internal_name"] + + @property + def accept_empty(self): + return self.validated_data.get("list_empty", False) diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index eb58ce898..3c16b22aa 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -1,3 +1,4 @@ +import uuid from urllib.parse import urlencode from django.contrib.auth.models import Permission @@ -7,7 +8,7 @@ from rest_framework.authtoken.models import Token from rest_framework.test import APITestCase -from sponsors.models import Sponsor +from sponsors.models import Sponsor, Sponsorship, TextAsset, ImgAsset from sponsors.models.enums import LogoPlacementChoices, PublisherChoices @@ -132,7 +133,6 @@ def test_bad_request_for_invalid_filters(self): class SponsorshipAssetsAPIListTests(APITestCase): - url = reverse_lazy("assets_list") def setUp(self): self.user = baker.make('users.User') @@ -140,6 +140,20 @@ def setUp(self): self.permission = Permission.objects.get(name='Can access sponsor placement API') self.user.user_permissions.add(self.permission) self.authorization = f'Token {token.key}' + self.internal_name = "txt_assets" + self.url = reverse_lazy("assets_list") + f"?internal_name={self.internal_name}" + self.sponsorship = baker.make(Sponsorship, sponsor__name='Sponsor 1') + self.sponsor = baker.make(Sponsor, name='Sponsor 2') + self.txt_asset = TextAsset.objects.create( + internal_name=self.internal_name, + uuid=uuid.uuid4(), + content_object=self.sponsorship, + ) + self.img_asset = ImgAsset.objects.create( + internal_name="img_assets", + uuid=uuid.uuid4(), + content_object=self.sponsorship, + ) def test_invalid_token(self): Token.objects.all().delete() @@ -165,3 +179,34 @@ def test_user_must_have_required_permission(self): self.user.user_permissions.remove(self.permission) response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) self.assertEqual(403, response.status_code) + + def test_bad_request_if_no_internal_name(self): + url = reverse_lazy("assets_list") + response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + self.assertEqual(400, response.status_code) + self.assertIn("internal_name", response.json()) + + def test_list_assets_by_internal_name(self): + # by default exclude assets with no value + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + data = response.json() + self.assertEqual(200, response.status_code) + self.assertEqual(0, len(data)) + # update asset to have a value + self.txt_asset.value = "Text Content" + self.txt_asset.save() + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + data = response.json() + self.assertEqual(1, len(data)) + self.assertEqual(data[0]["internal_name"], self.internal_name) + self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) + self.assertEqual(data[0]["value"], "Text Content") + self.assertEqual(data[0]["content_type"], "Sponsorship") + + def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): + self.url += "&list_empty=true" + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) + data = response.json() + self.assertEqual(1, len(data)) + self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) + self.assertEqual(data[0]["value"], "") From 264e8ba874b3f111e2a13f2a9a038f8b215d64de Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 12:50:11 -0300 Subject: [PATCH 6/9] Serialize image value as URL --- sponsors/api.py | 2 +- sponsors/serializers.py | 6 ++++++ sponsors/tests/test_api.py | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/sponsors/api.py b/sponsors/api.py index 1466a74cc..0d180be6d 100644 --- a/sponsors/api.py +++ b/sponsors/api.py @@ -68,7 +68,7 @@ def get(self, request, *args, **kwargs): assets = GenericAsset.objects.all_assets().filter( internal_name=assets_filter.by_internal_name).iterator() - assets = (a for a in assets if a.has_value or assets_filter.accept_empty) + assets = (a for a in assets if assets_filter.accept_empty or a.has_value) serializer = AssetSerializer(assets, many=True) return Response(serializer.data) diff --git a/sponsors/serializers.py b/sponsors/serializers.py index 59e379ca4..1d9e00337 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -20,6 +20,7 @@ class LogoPlacementSerializer(serializers.Serializer): class AssetSerializer(serializers.ModelSerializer): content_type = serializers.SerializerMethodField() + value = serializers.SerializerMethodField() class Meta: model = GenericAsset @@ -28,6 +29,11 @@ class Meta: def get_content_type(self, asset): return asset.content_type.name.title() + def get_value(self, asset): + if not asset.has_value: + return "" + return asset.value if not asset.is_file else asset.value.url + class FilterLogoPlacementsSerializer(serializers.Serializer): publisher = serializers.ChoiceField( diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index 3c16b22aa..8d8d7ba12 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -2,6 +2,7 @@ from urllib.parse import urlencode from django.contrib.auth.models import Permission +from django.core.files.uploadedfile import SimpleUploadedFile from django.urls import reverse_lazy from django.utils.text import slugify from model_bakery import baker @@ -155,6 +156,10 @@ def setUp(self): content_object=self.sponsorship, ) + def tearDown(self): + if self.img_asset.has_value: + self.img_asset.value.delete() + def test_invalid_token(self): Token.objects.all().delete() response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) @@ -192,11 +197,14 @@ def test_list_assets_by_internal_name(self): data = response.json() self.assertEqual(200, response.status_code) self.assertEqual(0, len(data)) + # update asset to have a value self.txt_asset.value = "Text Content" self.txt_asset.save() + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) data = response.json() + self.assertEqual(1, len(data)) self.assertEqual(data[0]["internal_name"], self.internal_name) self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) @@ -205,8 +213,22 @@ def test_list_assets_by_internal_name(self): def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.url += "&list_empty=true" + response = self.client.get(self.url, HTTP_AUTHORIZATION=self.authorization) data = response.json() + self.assertEqual(1, len(data)) self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) self.assertEqual(data[0]["value"], "") + + def test_serialize_img_value_as_url_to_image(self): + self.img_asset.value = SimpleUploadedFile(name='test_image.jpg', content=b"content", content_type='image/jpeg') + self.img_asset.save() + + url = reverse_lazy("assets_list") + f"?internal_name={self.img_asset.internal_name}" + response = self.client.get(url, HTTP_AUTHORIZATION=self.authorization) + data = response.json() + + self.assertEqual(1, len(data)) + self.assertEqual(data[0]["uuid"], str(self.img_asset.uuid)) + self.assertEqual(data[0]["value"], self.img_asset.value.url) From 2bdd1d6ec09c00c35f17f0cf43c518750ec89fcc Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 13:12:55 -0300 Subject: [PATCH 7/9] Serialize sponsor name within asset information --- sponsors/serializers.py | 9 ++++++++- sponsors/tests/test_api.py | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/sponsors/serializers.py b/sponsors/serializers.py index 1d9e00337..d52239d93 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -21,10 +21,11 @@ class LogoPlacementSerializer(serializers.Serializer): class AssetSerializer(serializers.ModelSerializer): content_type = serializers.SerializerMethodField() value = serializers.SerializerMethodField() + sponsor = serializers.SerializerMethodField() class Meta: model = GenericAsset - fields = ["internal_name", "uuid", "value", "content_type"] + fields = ["internal_name", "uuid", "value", "content_type", "sponsor"] def get_content_type(self, asset): return asset.content_type.name.title() @@ -34,6 +35,12 @@ def get_value(self, asset): return "" return asset.value if not asset.is_file else asset.value.url + def get_sponsor(self, asset): + if asset.from_sponsorship: + return asset.content_object.sponsor.name + else: + return asset.content_object.name + class FilterLogoPlacementsSerializer(serializers.Serializer): publisher = serializers.ChoiceField( diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index 8d8d7ba12..64572937f 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -153,7 +153,7 @@ def setUp(self): self.img_asset = ImgAsset.objects.create( internal_name="img_assets", uuid=uuid.uuid4(), - content_object=self.sponsorship, + content_object=self.sponsor, ) def tearDown(self): @@ -210,6 +210,7 @@ def test_list_assets_by_internal_name(self): self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) self.assertEqual(data[0]["value"], "Text Content") self.assertEqual(data[0]["content_type"], "Sponsorship") + self.assertEqual(data[0]["sponsor"], "Sponsor 1") def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.url += "&list_empty=true" @@ -220,6 +221,7 @@ def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.assertEqual(1, len(data)) self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) self.assertEqual(data[0]["value"], "") + self.assertEqual(data[0]["sponsor"], "Sponsor 1") def test_serialize_img_value_as_url_to_image(self): self.img_asset.value = SimpleUploadedFile(name='test_image.jpg', content=b"content", content_type='image/jpeg') @@ -232,3 +234,4 @@ def test_serialize_img_value_as_url_to_image(self): self.assertEqual(1, len(data)) self.assertEqual(data[0]["uuid"], str(self.img_asset.uuid)) self.assertEqual(data[0]["value"], self.img_asset.value.url) + self.assertEqual(data[0]["sponsor"], "Sponsor 2") From ab7da79d55165ea114815f9e69ce3a4a8d3b4c66 Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 16:38:54 -0300 Subject: [PATCH 8/9] Limit reportlab version to one that satisfies dependency but doesn't break imports --- base-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/base-requirements.txt b/base-requirements.txt index 66b693491..566263126 100644 --- a/base-requirements.txt +++ b/base-requirements.txt @@ -47,3 +47,4 @@ num2words==0.5.10 django-polymorphic==3.0.0 sorl-thumbnail==12.7.0 docxtpl==0.12.0 +reportlab==3.6.6 From ebc2227eac093f2ed4472b3c50a28abcdea3607f Mon Sep 17 00:00:00 2001 From: Bernardo Fontes Date: Fri, 25 Feb 2022 17:01:22 -0300 Subject: [PATCH 9/9] Also serializes sponsor slug --- sponsors/serializers.py | 17 ++++++++++++----- sponsors/tests/test_api.py | 3 +++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/sponsors/serializers.py b/sponsors/serializers.py index d52239d93..c0782c12a 100644 --- a/sponsors/serializers.py +++ b/sponsors/serializers.py @@ -22,10 +22,17 @@ class AssetSerializer(serializers.ModelSerializer): content_type = serializers.SerializerMethodField() value = serializers.SerializerMethodField() sponsor = serializers.SerializerMethodField() + sponsor_slug = serializers.SerializerMethodField() class Meta: model = GenericAsset - fields = ["internal_name", "uuid", "value", "content_type", "sponsor"] + fields = ["internal_name", "uuid", "value", "content_type", "sponsor", "sponsor_slug"] + + def _get_sponsor_object(self, asset): + if asset.from_sponsorship: + return asset.content_object.sponsor + else: + return asset.content_object def get_content_type(self, asset): return asset.content_type.name.title() @@ -36,10 +43,10 @@ def get_value(self, asset): return asset.value if not asset.is_file else asset.value.url def get_sponsor(self, asset): - if asset.from_sponsorship: - return asset.content_object.sponsor.name - else: - return asset.content_object.name + return self._get_sponsor_object(asset).name + + def get_sponsor_slug(self, asset): + return self._get_sponsor_object(asset).slug class FilterLogoPlacementsSerializer(serializers.Serializer): diff --git a/sponsors/tests/test_api.py b/sponsors/tests/test_api.py index 64572937f..caabd6aa1 100644 --- a/sponsors/tests/test_api.py +++ b/sponsors/tests/test_api.py @@ -211,6 +211,7 @@ def test_list_assets_by_internal_name(self): self.assertEqual(data[0]["value"], "Text Content") self.assertEqual(data[0]["content_type"], "Sponsorship") self.assertEqual(data[0]["sponsor"], "Sponsor 1") + self.assertEqual(data[0]["sponsor_slug"], "sponsor-1") def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.url += "&list_empty=true" @@ -222,6 +223,7 @@ def test_enable_to_filter_by_assets_with_no_value_via_querystring(self): self.assertEqual(data[0]["uuid"], str(self.txt_asset.uuid)) self.assertEqual(data[0]["value"], "") self.assertEqual(data[0]["sponsor"], "Sponsor 1") + self.assertEqual(data[0]["sponsor_slug"], "sponsor-1") def test_serialize_img_value_as_url_to_image(self): self.img_asset.value = SimpleUploadedFile(name='test_image.jpg', content=b"content", content_type='image/jpeg') @@ -235,3 +237,4 @@ def test_serialize_img_value_as_url_to_image(self): self.assertEqual(data[0]["uuid"], str(self.img_asset.uuid)) self.assertEqual(data[0]["value"], self.img_asset.value.url) self.assertEqual(data[0]["sponsor"], "Sponsor 2") + self.assertEqual(data[0]["sponsor_slug"], "sponsor-2")