Skip to content

Commit 9ebc0f6

Browse files
committed
Fix #190 -- Use field breakpoints and container width to calculate media query
1 parent 1050f7c commit 9ebc0f6

File tree

7 files changed

+95
-32
lines changed

7 files changed

+95
-32
lines changed

pictures/contrib/rest_framework.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
__all__ = ["PictureField"]
77

88
from pictures import utils
9-
from pictures.conf import get_settings
109
from pictures.models import PictureFieldFile, SimplePicture
1110

1211

@@ -33,13 +32,14 @@ def to_representation(self, obj: PictureFieldFile):
3332
"width": obj.width,
3433
"height": obj.height,
3534
}
35+
field = obj.field
3636

3737
# if the request has query parameters, filter the payload
3838
try:
3939
query_params: QueryDict = self.context["request"].GET
4040
except KeyError:
4141
ratios = self.aspect_ratios
42-
container = get_settings().CONTAINER_WIDTH
42+
container = field.container_width
4343
breakpoints = {}
4444
else:
4545
ratios = (
@@ -49,12 +49,12 @@ def to_representation(self, obj: PictureFieldFile):
4949
try:
5050
container = int(container)
5151
except TypeError:
52-
container = get_settings().CONTAINER_WIDTH
52+
container = field.container_width
5353
except ValueError as e:
5454
raise ValueError(f"Container width is not a number: {container}") from e
5555
breakpoints = {
5656
bp: int(query_params.get(f"{self.field_name}_{bp}"))
57-
for bp in get_settings().BREAKPOINTS
57+
for bp in field.breakpoints
5858
if f"{self.field_name}_{bp}" in query_params
5959
}
6060
if set(ratios) - set(self.aspect_ratios or obj.aspect_ratios.keys()):
@@ -71,7 +71,9 @@ def to_representation(self, obj: PictureFieldFile):
7171
for file_type, sizes in sources.items()
7272
if file_type in self.file_types or not self.file_types
7373
},
74-
"media": utils.sizes(container_width=container, **breakpoints),
74+
"media": utils.sizes(
75+
field=field, container_width=container, **breakpoints
76+
),
7577
}
7678
for ratio, sources in obj.aspect_ratios.items()
7779
if ratio in ratios or not ratios

pictures/templatetags/pictures.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
@register.simple_tag()
1313
def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
1414
settings = get_settings()
15-
container = container or settings.CONTAINER_WIDTH
15+
field = field_file.field
16+
container = container or field.container_width
1617
tmpl = loader.get_template("pictures/picture.html")
1718
breakpoints = {}
1819
picture_attrs = {}
@@ -29,7 +30,7 @@ def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
2930
f"Invalid ratio: {ratio}. Choices are: {', '.join(filter(None, field_file.aspect_ratios.keys()))}"
3031
) from e
3132
for key, value in kwargs.items():
32-
if key in settings.BREAKPOINTS:
33+
if key in field.breakpoints:
3334
breakpoints[key] = value
3435
elif key.startswith("picture_"):
3536
picture_attrs[key[8:]] = value
@@ -43,7 +44,7 @@ def picture(field_file, img_alt=None, ratio=None, container=None, **kwargs):
4344
"alt": img_alt,
4445
"ratio": (ratio or "3/2").replace("/", "x"),
4546
"sources": sources,
46-
"media": utils.sizes(container_width=container, **breakpoints),
47+
"media": utils.sizes(field=field, container_width=container, **breakpoints),
4748
"picture_attrs": picture_attrs,
4849
"img_attrs": img_attrs,
4950
"use_placeholders": settings.USE_PLACEHOLDERS,

pictures/utils.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,22 @@
1515
__all__ = ["sizes", "source_set", "placeholder"]
1616

1717

18-
def _grid(*, _columns=12, **breakpoint_sizes):
19-
settings = conf.get_settings()
20-
for key in breakpoint_sizes.keys() - settings.BREAKPOINTS.keys():
18+
def _grid(*, field, _columns=12, **breakpoint_sizes):
19+
for key in breakpoint_sizes.keys() - field.breakpoints:
2120
raise KeyError(
22-
f"Invalid breakpoint: {key}. Choices are: {', '.join(settings.BREAKPOINTS.keys())}"
21+
f"Invalid breakpoint: {key}. Choices are: {', '.join(field.breakpoints.keys())}"
2322
)
2423
prev_size = _columns
25-
for key, value in settings.BREAKPOINTS.items():
24+
for key, value in field.breakpoints.items():
2625
prev_size = breakpoint_sizes.get(key, prev_size)
2726
yield key, prev_size / _columns
2827

2928

30-
def _media_query(*, container_width: int | None = None, **breakpoints: int):
31-
settings = conf.get_settings()
29+
def _media_query(*, field, container_width: int | None = None, **breakpoints: int):
3230
prev_ratio = None
3331
prev_width = 0
3432
for key, ratio in breakpoints.items():
35-
width = settings.BREAKPOINTS[key]
33+
width = field.breakpoints[key]
3634
if container_width and width >= container_width:
3735
yield f"(min-width: {prev_width}px) and (max-width: {container_width - 1}px) {math.floor(ratio * 100)}vw"
3836
break
@@ -53,9 +51,13 @@ def _media_query(*, container_width: int | None = None, **breakpoints: int):
5351
yield f"{container_width}px" if container_width else "100vw"
5452

5553

56-
def sizes(*, cols=12, container_width: int | None = None, **breakpoints: int) -> str:
57-
breakpoints = dict(_grid(_columns=cols, **breakpoints))
58-
return ", ".join(_media_query(container_width=container_width, **breakpoints))
54+
def sizes(
55+
*, field, cols=12, container_width: int | None = None, **breakpoints: int
56+
) -> str:
57+
breakpoints = dict(_grid(field=field, _columns=cols, **breakpoints))
58+
return ", ".join(
59+
_media_query(field=field, container_width=container_width, **breakpoints)
60+
)
5961

6062

6163
def source_set(

tests/test_templatetags.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ def test_picture__additional_attrs__type_error(image_upload_file):
8888
assert "Invalid keyword argument: does_not_exist" in str(e.value)
8989

9090

91+
@pytest.mark.django_db
92+
def test_picture__field_defaults(image_upload_file):
93+
profile = Profile.objects.create(name="Spiderman", other_picture=image_upload_file)
94+
html = picture(profile.other_picture, ratio="3/2", small=2, medium=3)
95+
assert (
96+
'sizes="(min-width: 0px) and (max-width: 399px) 16vw, (min-width: 400px) and (max-width: 599px) 25vw, 150px"'
97+
in html
98+
)
99+
100+
91101
@pytest.mark.django_db
92102
def test_img_url(image_upload_file):
93103
profile = Profile.objects.create(name="Spiderman", picture=image_upload_file)

tests/test_utils.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

44
from pictures import utils
55
from pictures.models import SimplePicture
6+
from tests.testapp.models import SimpleModel
67

78

89
class TestGrid:
910
def test_default(self):
10-
assert list(utils._grid()) == [
11+
assert list(utils._grid(field=SimpleModel.picture.field)) == [
1112
("xs", 1.0),
1213
("s", 1.0),
1314
("m", 1.0),
@@ -16,7 +17,7 @@ def test_default(self):
1617
]
1718

1819
def test_small_up(self):
19-
assert list(utils._grid(xs=6)) == [
20+
assert list(utils._grid(field=SimpleModel.picture.field, xs=6)) == [
2021
("xs", 0.5),
2122
("s", 0.5),
2223
("m", 0.5),
@@ -25,7 +26,7 @@ def test_small_up(self):
2526
]
2627

2728
def test_mixed(self):
28-
assert list(utils._grid(s=6, l=9)) == [
29+
assert list(utils._grid(field=SimpleModel.picture.field, s=6, l=9)) == [
2930
("xs", 1.0),
3031
("s", 0.5),
3132
("m", 0.5),
@@ -35,50 +36,54 @@ def test_mixed(self):
3536

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

4142

4243
class TestSizes:
4344
def test_default(self):
44-
assert utils.sizes() == "100vw"
45+
assert utils.sizes(field=SimpleModel.picture.field) == "100vw"
4546

4647
def test_default__container(self):
4748
assert (
48-
utils.sizes(container_width=1200)
49+
utils.sizes(field=SimpleModel.picture.field, container_width=1200)
4950
== "(min-width: 0px) and (max-width: 1199px) 100vw, 1200px"
5051
)
5152

5253
def test_bottom_up(self):
53-
assert utils.sizes(xs=6) == "50vw"
54+
assert utils.sizes(field=SimpleModel.picture.field, xs=6) == "50vw"
5455

5556
def test_bottom_up__container(self):
5657
assert (
57-
utils.sizes(container_width=1200, xs=6)
58+
utils.sizes(field=SimpleModel.picture.field, container_width=1200, xs=6)
5859
== "(min-width: 0px) and (max-width: 1199px) 50vw, 600px"
5960
)
6061

6162
def test_medium_up(self):
62-
assert utils.sizes(s=6) == "(min-width: 0px) and (max-width: 767px) 100vw, 50vw"
63+
assert (
64+
utils.sizes(field=SimpleModel.picture.field, s=6)
65+
== "(min-width: 0px) and (max-width: 767px) 100vw, 50vw"
66+
)
6367

6468
def test_medium_up__container(self):
6569
assert (
66-
utils.sizes(container_width=1200, s=6)
70+
utils.sizes(field=SimpleModel.picture.field, container_width=1200, s=6)
6771
== "(min-width: 0px) and (max-width: 767px) 100vw,"
6872
" (min-width: 768px) and (max-width: 1199px) 50vw,"
6973
" 600px"
7074
)
7175

7276
def test_mixed(self):
7377
assert (
74-
utils.sizes(s=6, l=9) == "(min-width: 0px) and (max-width: 767px) 100vw,"
78+
utils.sizes(field=SimpleModel.picture.field, s=6, l=9)
79+
== "(min-width: 0px) and (max-width: 767px) 100vw,"
7580
" (min-width: 768px) and (max-width: 1199px) 50vw,"
7681
" 75vw"
7782
)
7883

7984
def test_mixed__container(self):
8085
assert (
81-
utils.sizes(container_width=1200, s=6, l=9)
86+
utils.sizes(field=SimpleModel.picture.field, container_width=1200, s=6, l=9)
8287
== "(min-width: 0px) and (max-width: 767px) 100vw,"
8388
" (min-width: 768px) and (max-width: 1199px) 75vw,"
8489
" 600px"
@@ -87,7 +92,7 @@ def test_mixed__container(self):
8792
def test_container__smaller_than_breakpoint(self):
8893
with pytest.warns() as records:
8994
assert (
90-
utils.sizes(container_width=500)
95+
utils.sizes(field=SimpleModel.picture.field, container_width=500)
9196
== "(min-width: 0px) and (max-width: 499px) 100vw, 500px"
9297
)
9398
assert str(records[0].message) == (
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Generated by Django 4.2.7 on 2024-12-14 13:15
2+
3+
from django.db import migrations
4+
5+
import pictures.models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("testapp", "0005_alter_profile_picture"),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name="profile",
17+
name="other_picture",
18+
field=pictures.models.PictureField(
19+
aspect_ratios=[None, "1/1", "3/2", "16/9"],
20+
blank=True,
21+
breakpoints=["small", "medium", "large"],
22+
container_width=600,
23+
file_types=["WEBP"],
24+
grid_columns=12,
25+
pixel_densities=[1, 2],
26+
upload_to="testapp/profile/",
27+
null=True,
28+
),
29+
),
30+
]

tests/testapp/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ class Profile(models.Model):
4040
blank=True,
4141
)
4242

43+
other_picture = PictureField(
44+
upload_to="testapp/profile/",
45+
aspect_ratios=[None, "1/1", "3/2", "16/9"],
46+
breakpoints={
47+
"small": 200,
48+
"medium": 400,
49+
"large": 800,
50+
},
51+
container_width=600,
52+
blank=True,
53+
null=True,
54+
)
55+
4356
def get_absolute_url(self):
4457
return reverse("profile_detail", kwargs={"pk": self.pk})
4558

0 commit comments

Comments
 (0)