Skip to content

Commit

Permalink
Support multiple aspect ratios in DRF and fallback to field definition (
Browse files Browse the repository at this point in the history
  • Loading branch information
codingjoe authored May 31, 2024
1 parent ec91252 commit 2f2e559
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 34 deletions.
54 changes: 26 additions & 28 deletions pictures/contrib/rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,17 @@ def to_representation(self, obj: PictureFieldFile):
"height": obj.height,
}

payload = {
**base_payload,
"ratios": {
ratio: {
"sources": {
f"image/{file_type.lower()}": sizes
for file_type, sizes in sources.items()
if file_type in self.file_types or not self.file_types
},
}
for ratio, sources in obj.aspect_ratios.items()
if ratio in self.aspect_ratios or not self.aspect_ratios
},
}

# if the request has query parameters, filter the payload
try:
query_params: QueryDict = self.context["request"].GET
except KeyError:
pass
ratios = self.aspect_ratios
container = get_settings().CONTAINER_WIDTH
breakpoints = {}
else:
ratio = query_params.get(f"{self.field_name}_ratio")
ratios = (
query_params.getlist(f"{self.field_name}_ratio")
) or self.aspect_ratios
container = query_params.get(f"{self.field_name}_container")
try:
container = int(container)
Expand All @@ -68,16 +57,25 @@ def to_representation(self, obj: PictureFieldFile):
for bp in get_settings().BREAKPOINTS
if f"{self.field_name}_{bp}" in query_params
}
if ratio is not None:
try:
payload["ratios"] = {ratio: payload["ratios"][ratio]}
except KeyError as e:
raise ValueError(
f"Invalid ratio: {ratio}. Choices are: {', '.join(filter(None, obj.aspect_ratios.keys()))}"
) from e
else:
payload["ratios"][ratio]["media"] = utils.sizes(
container_width=container, **breakpoints
)
if set(ratios) - set(self.aspect_ratios or obj.aspect_ratios.keys()):
raise ValueError(
f"Invalid ratios: {', '.join(ratios)}. Choices are: {', '.join(filter(None, obj.aspect_ratios.keys()))}"
)

payload = {
**base_payload,
"ratios": {
ratio: {
"sources": {
f"image/{file_type.lower()}": sizes
for file_type, sizes in sources.items()
if file_type in self.file_types or not self.file_types
},
"media": utils.sizes(container_width=container, **breakpoints),
}
for ratio, sources in obj.aspect_ratios.items()
if ratio in ratios or not ratios
},
}

return json.loads(json.dumps(payload, default=default))
97 changes: 91 additions & 6 deletions tests/contrib/test_rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ def test_to_representation(self, image_upload_file, settings):
"600": "/media/testapp/profile/image/600w.webp",
"700": "/media/testapp/profile/image/700w.webp",
}
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
"1/1": {
"sources": {
Expand All @@ -92,7 +93,8 @@ def test_to_representation(self, image_upload_file, settings):
"600": "/media/testapp/profile/image/1/600w.webp",
"700": "/media/testapp/profile/image/1/700w.webp",
}
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
"3/2": {
"sources": {
Expand All @@ -106,7 +108,8 @@ def test_to_representation(self, image_upload_file, settings):
"600": "/media/testapp/profile/image/3_2/600w.webp",
"700": "/media/testapp/profile/image/3_2/700w.webp",
}
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
"16/9": {
"sources": {
Expand All @@ -120,7 +123,8 @@ def test_to_representation(self, image_upload_file, settings):
"600": "/media/testapp/profile/image/16_9/600w.webp",
"700": "/media/testapp/profile/image/16_9/700w.webp",
}
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
},
}
Expand Down Expand Up @@ -179,7 +183,7 @@ def test_to_representation__raise_value_error(
with pytest.raises(ValueError) as e:
serializer.data["image"]

assert str(e.value) == "Invalid ratio: 21/11. Choices are: 1/1, 3/2, 16/9"
assert str(e.value) == "Invalid ratios: 21/11. Choices are: 1/1, 3/2, 16/9"

@pytest.mark.django_db
def test_to_representation__blank(self, rf, image_upload_file, settings):
Expand All @@ -195,6 +199,86 @@ def test_to_representation__blank(self, rf, image_upload_file, settings):

assert serializer.data["image"] is None

@pytest.mark.django_db
def test_to_representation__no_get_params(self, rf, image_upload_file, settings):
settings.PICTURES["USE_PLACEHOLDERS"] = False

profile = models.Profile.objects.create(picture=image_upload_file)
request = rf.get("/")
request.GET._mutable = True
request.GET["foo"] = "bar"
serializer = ProfileSerializer(profile, context={"request": request})
assert serializer.data["image_mobile"] == {
"url": "/media/testapp/profile/image.png",
"width": 800,
"height": 800,
"ratios": {
"3/2": {
"sources": {
"image/webp": {
"800": "/media/testapp/profile/image/3_2/800w.webp",
"100": "/media/testapp/profile/image/3_2/100w.webp",
"200": "/media/testapp/profile/image/3_2/200w.webp",
"300": "/media/testapp/profile/image/3_2/300w.webp",
"400": "/media/testapp/profile/image/3_2/400w.webp",
"500": "/media/testapp/profile/image/3_2/500w.webp",
"600": "/media/testapp/profile/image/3_2/600w.webp",
"700": "/media/testapp/profile/image/3_2/700w.webp",
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
}
},
}

@pytest.mark.django_db
def test_to_representation__multiple_ratios(self, rf, image_upload_file, settings):
settings.PICTURES["USE_PLACEHOLDERS"] = False

profile = models.Profile.objects.create(picture=image_upload_file)
request = rf.get("/")
request.GET._mutable = True
request.GET.setlist("image_ratio", ["3/2", "16/9"])
serializer = ProfileSerializer(profile, context={"request": request})
print(serializer.data["image"])
assert serializer.data["image"] == {
"url": "/media/testapp/profile/image.png",
"width": 800,
"height": 800,
"ratios": {
"3/2": {
"sources": {
"image/webp": {
"800": "/media/testapp/profile/image/3_2/800w.webp",
"100": "/media/testapp/profile/image/3_2/100w.webp",
"200": "/media/testapp/profile/image/3_2/200w.webp",
"300": "/media/testapp/profile/image/3_2/300w.webp",
"400": "/media/testapp/profile/image/3_2/400w.webp",
"500": "/media/testapp/profile/image/3_2/500w.webp",
"600": "/media/testapp/profile/image/3_2/600w.webp",
"700": "/media/testapp/profile/image/3_2/700w.webp",
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
"16/9": {
"sources": {
"image/webp": {
"800": "/media/testapp/profile/image/16_9/800w.webp",
"100": "/media/testapp/profile/image/16_9/100w.webp",
"200": "/media/testapp/profile/image/16_9/200w.webp",
"300": "/media/testapp/profile/image/16_9/300w.webp",
"400": "/media/testapp/profile/image/16_9/400w.webp",
"500": "/media/testapp/profile/image/16_9/500w.webp",
"600": "/media/testapp/profile/image/16_9/600w.webp",
"700": "/media/testapp/profile/image/16_9/700w.webp",
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
},
},
}

@pytest.mark.django_db
def test_to_representation__with_container(self, rf, image_upload_file, settings):
settings.PICTURES["USE_PLACEHOLDERS"] = False
Expand Down Expand Up @@ -304,7 +388,8 @@ def test_to_representation__with_prefiltered_aspect_ratio_and_source(
"600": "/media/testapp/profile/image/3_2/600w.webp",
"700": "/media/testapp/profile/image/3_2/700w.webp",
}
}
},
"media": "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px",
}
},
}

0 comments on commit 2f2e559

Please sign in to comment.