Skip to content

Commit

Permalink
Fix #190 -- Use field breakpoints and container width to calculate me…
Browse files Browse the repository at this point in the history
…dia query
  • Loading branch information
codingjoe committed Dec 14, 2024
1 parent 1050f7c commit 9ebc0f6
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 32 deletions.
12 changes: 7 additions & 5 deletions pictures/contrib/rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
__all__ = ["PictureField"]

from pictures import utils
from pictures.conf import get_settings
from pictures.models import PictureFieldFile, SimplePicture


Expand All @@ -33,13 +32,14 @@ def to_representation(self, obj: PictureFieldFile):
"width": obj.width,
"height": obj.height,
}
field = obj.field

# if the request has query parameters, filter the payload
try:
query_params: QueryDict = self.context["request"].GET
except KeyError:
ratios = self.aspect_ratios
container = get_settings().CONTAINER_WIDTH
container = field.container_width
breakpoints = {}
else:
ratios = (
Expand All @@ -49,12 +49,12 @@ def to_representation(self, obj: PictureFieldFile):
try:
container = int(container)
except TypeError:
container = get_settings().CONTAINER_WIDTH
container = field.container_width
except ValueError as e:
raise ValueError(f"Container width is not a number: {container}") from e
breakpoints = {
bp: int(query_params.get(f"{self.field_name}_{bp}"))
for bp in get_settings().BREAKPOINTS
for bp in field.breakpoints
if f"{self.field_name}_{bp}" in query_params
}
if set(ratios) - set(self.aspect_ratios or obj.aspect_ratios.keys()):
Expand All @@ -71,7 +71,9 @@ def to_representation(self, obj: PictureFieldFile):
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),
"media": utils.sizes(
field=field, container_width=container, **breakpoints
),
}
for ratio, sources in obj.aspect_ratios.items()
if ratio in ratios or not ratios
Expand Down
7 changes: 4 additions & 3 deletions pictures/templatetags/pictures.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
@register.simple_tag()
def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
settings = get_settings()
container = container or settings.CONTAINER_WIDTH
field = field_file.field
container = container or field.container_width
tmpl = loader.get_template("pictures/picture.html")
breakpoints = {}
picture_attrs = {}
Expand All @@ -29,7 +30,7 @@ def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
f"Invalid ratio: {ratio}. Choices are: {', '.join(filter(None, field_file.aspect_ratios.keys()))}"
) from e
for key, value in kwargs.items():
if key in settings.BREAKPOINTS:
if key in field.breakpoints:
breakpoints[key] = value
elif key.startswith("picture_"):
picture_attrs[key[8:]] = value
Expand All @@ -43,7 +44,7 @@ def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
"alt": img_alt,
"ratio": (ratio or "3/2").replace("/", "x"),
"sources": sources,
"media": utils.sizes(container_width=container, **breakpoints),
"media": utils.sizes(field=field, container_width=container, **breakpoints),
"picture_attrs": picture_attrs,
"img_attrs": img_attrs,
"use_placeholders": settings.USE_PLACEHOLDERS,
Expand Down
24 changes: 13 additions & 11 deletions pictures/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,22 @@
__all__ = ["sizes", "source_set", "placeholder"]


def _grid(*, _columns=12, **breakpoint_sizes):
settings = conf.get_settings()
for key in breakpoint_sizes.keys() - settings.BREAKPOINTS.keys():
def _grid(*, field, _columns=12, **breakpoint_sizes):
for key in breakpoint_sizes.keys() - field.breakpoints:
raise KeyError(
f"Invalid breakpoint: {key}. Choices are: {', '.join(settings.BREAKPOINTS.keys())}"
f"Invalid breakpoint: {key}. Choices are: {', '.join(field.breakpoints.keys())}"
)
prev_size = _columns
for key, value in settings.BREAKPOINTS.items():
for key, value in field.breakpoints.items():
prev_size = breakpoint_sizes.get(key, prev_size)
yield key, prev_size / _columns


def _media_query(*, container_width: int | None = None, **breakpoints: int):
settings = conf.get_settings()
def _media_query(*, field, container_width: int | None = None, **breakpoints: int):
prev_ratio = None
prev_width = 0
for key, ratio in breakpoints.items():
width = settings.BREAKPOINTS[key]
width = field.breakpoints[key]
if container_width and width >= container_width:
yield f"(min-width: {prev_width}px) and (max-width: {container_width - 1}px) {math.floor(ratio * 100)}vw"
break
Expand All @@ -53,9 +51,13 @@ def _media_query(*, container_width: int | None = None, **breakpoints: int):
yield f"{container_width}px" if container_width else "100vw"


def sizes(*, cols=12, container_width: int | None = None, **breakpoints: int) -> str:
breakpoints = dict(_grid(_columns=cols, **breakpoints))
return ", ".join(_media_query(container_width=container_width, **breakpoints))
def sizes(
*, field, cols=12, container_width: int | None = None, **breakpoints: int
) -> str:
breakpoints = dict(_grid(field=field, _columns=cols, **breakpoints))
return ", ".join(
_media_query(field=field, container_width=container_width, **breakpoints)
)


def source_set(
Expand Down
10 changes: 10 additions & 0 deletions tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ def test_picture__additional_attrs__type_error(image_upload_file):
assert "Invalid keyword argument: does_not_exist" in str(e.value)


@pytest.mark.django_db
def test_picture__field_defaults(image_upload_file):
profile = Profile.objects.create(name="Spiderman", other_picture=image_upload_file)
html = picture(profile.other_picture, ratio="3/2", small=2, medium=3)
assert (
'sizes="(min-width: 0px) and (max-width: 399px) 16vw, (min-width: 400px) and (max-width: 599px) 25vw, 150px"'
in html
)


@pytest.mark.django_db
def test_img_url(image_upload_file):
profile = Profile.objects.create(name="Spiderman", picture=image_upload_file)
Expand Down
31 changes: 18 additions & 13 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

from pictures import utils
from pictures.models import SimplePicture
from tests.testapp.models import SimpleModel


class TestGrid:
def test_default(self):
assert list(utils._grid()) == [
assert list(utils._grid(field=SimpleModel.picture.field)) == [
("xs", 1.0),
("s", 1.0),
("m", 1.0),
Expand All @@ -16,7 +17,7 @@ def test_default(self):
]

def test_small_up(self):
assert list(utils._grid(xs=6)) == [
assert list(utils._grid(field=SimpleModel.picture.field, xs=6)) == [
("xs", 0.5),
("s", 0.5),
("m", 0.5),
Expand All @@ -25,7 +26,7 @@ def test_small_up(self):
]

def test_mixed(self):
assert list(utils._grid(s=6, l=9)) == [
assert list(utils._grid(field=SimpleModel.picture.field, s=6, l=9)) == [
("xs", 1.0),
("s", 0.5),
("m", 0.5),
Expand All @@ -35,50 +36,54 @@ def test_mixed(self):

def test_key_error(self):
with pytest.raises(KeyError) as e:
list(utils._grid(xxxxl=6))
list(utils._grid(field=SimpleModel.picture.field, xxxxl=6))
assert "Invalid breakpoint: xxxxl. Choices are: xs, s, m, l, xl" in str(e.value)


class TestSizes:
def test_default(self):
assert utils.sizes() == "100vw"
assert utils.sizes(field=SimpleModel.picture.field) == "100vw"

def test_default__container(self):
assert (
utils.sizes(container_width=1200)
utils.sizes(field=SimpleModel.picture.field, container_width=1200)
== "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px"
)

def test_bottom_up(self):
assert utils.sizes(xs=6) == "50vw"
assert utils.sizes(field=SimpleModel.picture.field, xs=6) == "50vw"

def test_bottom_up__container(self):
assert (
utils.sizes(container_width=1200, xs=6)
utils.sizes(field=SimpleModel.picture.field, container_width=1200, xs=6)
== "(min-width: 0px) and (max-width: 1199px) 50vw, 600px"
)

def test_medium_up(self):
assert utils.sizes(s=6) == "(min-width: 0px) and (max-width: 767px) 100vw, 50vw"
assert (
utils.sizes(field=SimpleModel.picture.field, s=6)
== "(min-width: 0px) and (max-width: 767px) 100vw, 50vw"
)

def test_medium_up__container(self):
assert (
utils.sizes(container_width=1200, s=6)
utils.sizes(field=SimpleModel.picture.field, container_width=1200, s=6)
== "(min-width: 0px) and (max-width: 767px) 100vw,"
" (min-width: 768px) and (max-width: 1199px) 50vw,"
" 600px"
)

def test_mixed(self):
assert (
utils.sizes(s=6, l=9) == "(min-width: 0px) and (max-width: 767px) 100vw,"
utils.sizes(field=SimpleModel.picture.field, s=6, l=9)
== "(min-width: 0px) and (max-width: 767px) 100vw,"
" (min-width: 768px) and (max-width: 1199px) 50vw,"
" 75vw"
)

def test_mixed__container(self):
assert (
utils.sizes(container_width=1200, s=6, l=9)
utils.sizes(field=SimpleModel.picture.field, container_width=1200, s=6, l=9)
== "(min-width: 0px) and (max-width: 767px) 100vw,"
" (min-width: 768px) and (max-width: 1199px) 75vw,"
" 600px"
Expand All @@ -87,7 +92,7 @@ def test_mixed__container(self):
def test_container__smaller_than_breakpoint(self):
with pytest.warns() as records:
assert (
utils.sizes(container_width=500)
utils.sizes(field=SimpleModel.picture.field, container_width=500)
== "(min-width: 0px) and (max-width: 499px) 100vw, 500px"
)
assert str(records[0].message) == (
Expand Down
30 changes: 30 additions & 0 deletions tests/testapp/migrations/0006_profile_other_picture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 4.2.7 on 2024-12-14 13:15

from django.db import migrations

import pictures.models


class Migration(migrations.Migration):

dependencies = [
("testapp", "0005_alter_profile_picture"),
]

operations = [
migrations.AddField(
model_name="profile",
name="other_picture",
field=pictures.models.PictureField(
aspect_ratios=[None, "1/1", "3/2", "16/9"],
blank=True,
breakpoints=["small", "medium", "large"],
container_width=600,
file_types=["WEBP"],
grid_columns=12,
pixel_densities=[1, 2],
upload_to="testapp/profile/",
null=True,
),
),
]
13 changes: 13 additions & 0 deletions tests/testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ class Profile(models.Model):
blank=True,
)

other_picture = PictureField(
upload_to="testapp/profile/",
aspect_ratios=[None, "1/1", "3/2", "16/9"],
breakpoints={
"small": 200,
"medium": 400,
"large": 800,
},
container_width=600,
blank=True,
null=True,
)

def get_absolute_url(self):
return reverse("profile_detail", kwargs={"pk": self.pk})

Expand Down

0 comments on commit 9ebc0f6

Please sign in to comment.