diff --git a/CHANGELOG.md b/CHANGELOG.md index a829ef8a..3933d297 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,28 @@ # Changelog ## Pre-release + +### Implemented enhancements +- CI-677 - Add CTA link field to Capital Investment homepage hero +- no ticket - Add cache supoort to draft pages + +## Hotfix +- No ticket - v3-cipipeline manifest.yml file fix + +## [2020.03.23](https://github.com/uktrade/directory-cms/releases/tag/2020.03.23) +[Full Changelog](https://github.com/uktrade/directory-cms/compare/2020.03.06...2020.03.23) + +### Implemented enhancements - no ticket - Handle non-serializable page requested via /api/pages// - no ticket - Upgrade bleach to fix vulnerability +- no ticket - return only pks on /api/pages/ +- no ticket - Add caching to /api/pages// ## [2020.03.06](https://github.com/uktrade/directory-cms/releases/tag/2020.03.06) [Full Changelog](https://github.com/uktrade/directory-cms/compare/2020.03.04...2020.03.06) ### Implemented enhancements - CMS-1859 - Simplify caching system -- no ticket - return only pks on /api/pages/ -- no ticket - Add caching to /api/pages// - no ticket - Remove region support ## [2020.03.04](https://github.com/uktrade/directory-cms/releases/tag/2020.03.04) diff --git a/core/cache.py b/core/cache.py index 83ff74a5..54e10d81 100644 --- a/core/cache.py +++ b/core/cache.py @@ -129,6 +129,18 @@ def populate_sync(cls, instance): data=serializer.data, lang=language_code, ) + if instance.has_unpublished_changes: + draft_instance = instance.get_latest_nested_revision_as_page() + draft_serializer = serializer_class( + instance=draft_instance, + context={'is_draft': True} + ) + page_cache.set( + page_id=instance.id, + data=draft_serializer.data, + lang=language_code, + draft_version=True + ) @classmethod def delete(cls, instance): @@ -138,6 +150,11 @@ def delete(cls, instance): page_id=instance.id, lang=language_code, ) + page_cache.delete( + page_id=instance.id, + lang=language_code, + draft_version=True + ) class PageIDCache: diff --git a/core/fields.py b/core/fields.py index 1bac6670..3131f6fa 100644 --- a/core/fields.py +++ b/core/fields.py @@ -15,17 +15,13 @@ def to_representation(self, instance): class MetaDictField(fields.DictField): def get_attribute(self, instance): - if 'request' in self.context: - is_draft = helpers.is_draft_requested(self.context['request']) - else: - is_draft = False return { 'languages': [ (code, label) for (code, label) in settings.LANGUAGES_LOCALIZED if code in instance.specific.translated_languages ], 'url': instance.specific.get_url( - is_draft=is_draft, + is_draft=self.context.get('is_draft', False), language_code=settings.LANGUAGE_CODE, ), 'slug': instance.slug, diff --git a/core/views.py b/core/views.py index b02faa21..33b63367 100644 --- a/core/views.py +++ b/core/views.py @@ -1,3 +1,5 @@ +from logging import getLogger + from django_filters.rest_framework import DjangoFilterBackend from rest_framework.exceptions import ValidationError from rest_framework.renderers import JSONRenderer @@ -22,6 +24,9 @@ from core.serializer_mapping import MODELS_SERIALIZERS_MAPPING +logger = getLogger(__name__) + + class PageNotSerializableError(NotImplementedError): pass @@ -89,14 +94,13 @@ def detail_view(self, request, **kwargs): # Exit early if there are any issues self.check_parameter_validity() + variation_kwargs = {'lang': translation.get_language()} + if helpers.is_draft_requested(request): - return super().detail_view(request, pk=None) + variation_kwargs['draft_version'] = True # Return a cached response if one is available - cached_data = cache.PageCache.get( - page_id=self.object_id, - lang=translation.get_language(), - ) + cached_data = cache.PageCache.get(page_id=self.object_id, **variation_kwargs) if cached_data: cached_response = helpers.CachedResponse(cached_data) cached_response['etag'] = cached_data.get('etag', None) @@ -108,15 +112,22 @@ def detail_view(self, request, **kwargs): # No cached response available response = super().detail_view(request, pk=None) + if response.status_code == 200: # Reuse the already-fetched object to populate the cache cache.CachePopulator.populate_async(self.get_object()) + logger.warn(f'Page cache miss') # No etag is set for this response because creating one is expensive. # If API caching is enabled, one will be added to the cached version # created above. return response + def get_serializer_context(self): + context = super().get_serializer_context() + context['is_draft'] = helpers.is_draft_requested(self.request) + return context + class PagesOptionalDraftAPIEndpoint(APIEndpointBase): def listing_view(self, request): diff --git a/great_international/migrations/0093_auto_20200414_1626.py b/great_international/migrations/0093_auto_20200414_1626.py new file mode 100644 index 00000000..fc735206 --- /dev/null +++ b/great_international/migrations/0093_auto_20200414_1626.py @@ -0,0 +1,58 @@ +# Generated by Django 2.2.10 on 2020-04-14 16:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('great_international', '0092_auto_20200127_1223'), + ] + + operations = [ + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link', + field=models.CharField(blank=True, max_length=255), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_ar', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_de', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_en_gb', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_es', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_fr', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_ja', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_pt', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='internationalcapitalinvestlandingpage', + name='hero_cta_link_zh_hans', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/great_international/models/capital_invest.py b/great_international/models/capital_invest.py index e8821253..96421237 100644 --- a/great_international/models/capital_invest.py +++ b/great_international/models/capital_invest.py @@ -149,6 +149,7 @@ class InternationalCapitalInvestLandingPage( ) hero_subtitle = models.CharField(max_length=255, blank=True) hero_cta_text = models.CharField(max_length=255, blank=True) + hero_cta_link = models.CharField(max_length=255, blank=True) featured_description = models.TextField(max_length=255, blank=True) diff --git a/great_international/panels/capital_invest.py b/great_international/panels/capital_invest.py index 138f5ad3..c01b7158 100644 --- a/great_international/panels/capital_invest.py +++ b/great_international/panels/capital_invest.py @@ -21,7 +21,8 @@ class InternationalCapitalInvestLandingPagePanels: FieldPanel('hero_title'), FieldPanel('hero_subheading'), FieldPanel('hero_subtitle'), - FieldPanel('hero_cta_text') + FieldPanel('hero_cta_text'), + FieldPanel('hero_cta_link'), ] ), MultiFieldPanel( diff --git a/great_international/serializers.py b/great_international/serializers.py index febee5a6..651e304f 100644 --- a/great_international/serializers.py +++ b/great_international/serializers.py @@ -1316,6 +1316,7 @@ class InternationalCapitalInvestLandingPageSerializer(BasePageSerializer, HeroSe hero_subheading = serializers.CharField(max_length=255) hero_subtitle = serializers.CharField(max_length=255) hero_cta_text = serializers.CharField(max_length=255) + hero_cta_link = serializers.CharField(max_length=255) reason_to_invest_section_title = serializers.CharField(max_length=255) reason_to_invest_section_intro = serializers.CharField(max_length=255) diff --git a/great_international/translation.py b/great_international/translation.py index c744033c..1a3d15c8 100644 --- a/great_international/translation.py +++ b/great_international/translation.py @@ -415,6 +415,7 @@ class InternationalCapitalInvestLandingPageTranslationOptions( 'hero_subheading', 'hero_subtitle', 'hero_cta_text', + 'hero_cta_link', 'reason_to_invest_section_title', 'reason_to_invest_section_intro', diff --git a/manifest.yml b/manifest.yml index 5428a7c2..96ad19e8 100644 --- a/manifest.yml +++ b/manifest.yml @@ -1,4 +1,5 @@ --- applications: - - buildpack: python_buildpack + - buildpacks: + - python_buildpack timeout: 180 diff --git a/tests/core/test_cache.py b/tests/core/test_cache.py index 2d29e143..17cea425 100644 --- a/tests/core/test_cache.py +++ b/tests/core/test_cache.py @@ -160,16 +160,23 @@ class TestSubscriber(cache.DatabaseCacheSubscriber): model_classes = [Page] instance = mock.Mock( - id=1, slug='some-slug', service_name='thing', + id=2, slug='some-slug', service_name='thing', translated_languages=['en-gb'] ) TestSubscriber.delete(sender=None, instance=instance) - assert mock_delete.call_count == 1 - assert mock_delete.call_args == mock.call( - page_id=1, - lang='en-gb', - ) + assert mock_delete.call_count == 2 + assert mock_delete.call_args_list == [ + mock.call( + page_id=2, + lang='en-gb', + ), + mock.call( + page_id=2, + lang='en-gb', + draft_version=True, + ) + ] @mock.patch('django.core.cache.cache.set') diff --git a/tests/core/test_fields.py b/tests/core/test_fields.py index 7312b49e..adbd7522 100644 --- a/tests/core/test_fields.py +++ b/tests/core/test_fields.py @@ -5,7 +5,6 @@ from wagtail.core.fields import StreamField from core import fields -from core import permissions from great_international.serializers import InternationalSectorPageSerializer, InternationalArticlePageSerializer from tests.great_international.factories import InternationalSectorPageFactory @@ -103,10 +102,9 @@ def test_meta_field_draft(international_root_page, rf): parent=international_root_page, slug='test-slug', ) - request = rf.get('/', {permissions.DraftTokenPermisison.TOKEN_PARAM: '1'}) serializer = InternationalSectorPageSerializer( instance=sector_page, - context={'request': request} + context={'is_draft': True} ) assert serializer.data['meta']['url'] == sector_page.get_url(is_draft=True) diff --git a/tests/great_international/test_serializers.py b/tests/great_international/test_serializers.py index 5858df9d..60062b42 100644 --- a/tests/great_international/test_serializers.py +++ b/tests/great_international/test_serializers.py @@ -1427,6 +1427,31 @@ def test_new_int_home_page_has_related_capital_invest(rf, international_root_pag assert serializer.data['related_page_invest_capital']['meta']['slug'] == 'capital-invest' +@pytest.mark.django_db +def test_capital_invest_landing_page_has_cta(rf, international_root_page, image): + capital_invest_landing_page = InternationalCapitalInvestLandingPageFactory( + parent=international_root_page, + slug='capital-invest', + hero_title='Hero title', + hero_image=image, + hero_subheading='Hero subheading', + hero_subtitle='Hero subtitle', + hero_cta_text='click here', + hero_cta_link='/hero/cta/link', + ) + + serializer = InternationalCapitalInvestLandingPageSerializer( + instance=capital_invest_landing_page, + context={'request': rf.get('/')}, + ) + + assert serializer.data['hero_title'] == 'Hero title' + assert serializer.data['hero_subheading'] == 'Hero subheading' + assert serializer.data['hero_subtitle'] == 'Hero subtitle' + assert serializer.data['hero_cta_text'] == 'click here' + assert serializer.data['hero_cta_link'] == '/hero/cta/link' + + @pytest.mark.django_db def test_new_int_home_page_has_related_trade(rf, international_root_page, image): trade = InternationalTradeHomePageFactory(