Skip to content

Commit

Permalink
Merge branch 'release' into 'master'
Browse files Browse the repository at this point in the history
Release 71.01

See merge request buckinghamshire-council/bc!708
  • Loading branch information
alxbridge committed Jun 17, 2024
2 parents ce12c67 + 19b3e13 commit 06bf515
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 11 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Release History

## Unreleased
## 71.01 (2024-06-17)

Compare: <https://git.torchbox.com/buckinghamshire-council/bc/compare/70.01...HEAD>
Compare: <https://git.torchbox.com/buckinghamshire-council/bc/compare/71.00...71.01>

- Validate Talentlink-imported images as if uploaded through the Wagtail admin

## 71.00 (2024-06-03)

Expand Down
53 changes: 45 additions & 8 deletions bc/images/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging
import warnings
from io import BytesIO
from mimetypes import guess_extension

Expand All @@ -8,6 +10,9 @@
from wagtail.models import Collection

import requests
from PIL.Image import DecompressionBombError, DecompressionBombWarning

logger = logging.getLogger(__name__)


class CustomImage(AbstractImage):
Expand All @@ -25,6 +30,33 @@ class Meta:
unique_together = [["image", "filter_spec", "focal_point_key"]]


class ImageImportException(Exception):
pass


def get_validated_image_form(image_file, image_data):
"""Validate imported images as if uploaded through the Wagtail admin"""
from wagtail.images.forms import get_image_form

CustomImageForm = get_image_form(CustomImage)
image_form = CustomImageForm(image_data, {"file": image_file})
with warnings.catch_warnings():
# Escalate DecompressionBombWarning to error level
warnings.simplefilter("error", DecompressionBombWarning)
try:
if not image_form.is_valid():
for field_name, errors in image_form.errors.items():
logger.warning(
f"Image validation failed in {field_name} importing {image_file.name} from TalentLink: {errors}"
)
except (DecompressionBombError, DecompressionBombWarning) as e:
logger.warning(
f"Image validation failed importing {image_file.name} from TalentLink: {e}"
)
image_form.add_error("file", e)
return image_form


def import_image_from_url(
title, url, filename, talentlink_image_id=None, collection_name="imported"
):
Expand All @@ -42,12 +74,17 @@ def import_image_from_url(
image_file = ImageFile(
BytesIO(image_file_response.content), name=image_filename
)
new_image = CustomImage(
title=title,
file=image_file,
talentlink_image_id=talentlink_image_id,
collection=collection,
)
# new_image.collection = collection
new_image.save()
image_data = {
"title": title,
"talentlink_image_id": talentlink_image_id,
"collection": collection,
}

image_form = get_validated_image_form(image_file, image_data)
if image_form.errors:
raise ImageImportException(
f"File rejected due to validation errors:\n{image_form.errors.as_text()}"
)

new_image = image_form.save()
return new_image
41 changes: 40 additions & 1 deletion bc/recruitment_api/tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from bc.documents.models import CustomDocument
from bc.documents.tests.fixtures import DocumentFactory
from bc.images.models import CustomImage
from bc.images.models import CustomImage, ImageImportException
from bc.images.tests.fixtures import ImageFactory, mock_import_image_from_url
from bc.recruitment.constants import JOB_BOARD_CHOICES
from bc.recruitment.models import JobSubcategory, TalentLinkJob
Expand Down Expand Up @@ -1319,3 +1319,42 @@ def test_logo_is_not_deleted_if_another_job_is_using_it(
CustomImage.objects.get(id=logo.id),
msg="Job 2 and its logo should be unaffected when Job 1 is deleted.",
)

def test_bad_logo_is_not_imported(
self, mock_import_image_from_url, mock_get_client
):
original_image_count = CustomImage.objects.all().count()
advertisements = [
get_advertisement(talentlink_id=1, title="New title 1"),
get_advertisement(talentlink_id=2, title="New title 2"),
]

job_1_get_logo_response = [get_logo(id="aaa")]
job_2_get_logo_response = [get_logo(id="bbb")]
logos = [job_1_get_logo_response, job_2_get_logo_response]

mock_import_image_from_url.side_effect = ImageImportException(
"File rejected due to validation errors."
)

mock_get_client.return_value = self.get_mocked_client(
advertisements, logos=logos
)

out = StringIO()
call_command("import_jobs", stdout=out)
out.seek(0)
output = out.read()

self.assertIn("2 new jobs created", output)
self.assertIn("0 new images imported", output)
self.assertEqual(CustomImage.objects.all().count(), original_image_count)
self.assertEqual(
TalentLinkJob.objects.get(talentlink_id=1).logo,
None,
)
self.assertEqual(
TalentLinkJob.objects.get(talentlink_id=2).logo,
None,
)
self.assertIn("Error occurred while importing logo image", output)
4 changes: 4 additions & 0 deletions docs/recruitment-site.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ This is a concatenation of multiple `custom_field` fields, whose format is a 'la

This is either the text of the first `<p>` element in the first custom_field value from above, or the entire first custom_field value, if it is formatted as plain text.

#### Logo

Logo images are pulled from their remote location and imported to Wagtail's image library. If an image doesn't pass Wagtail's validation, it's rejected and the field left blank. This means that some images allowed by TalentLink may not be imported - e.g. if they exceed the size allowed by the `MAX_IMAGE_PIXELS` setting.

## Job Alerts

### Management command
Expand Down

0 comments on commit 06bf515

Please sign in to comment.