Repository: codingjoe/django-pictures (language: python)

Issue:
DRF Serializer Support
This is a long-shot suggestion.

How do you feel about including serializer support like it "was" provided [here](https://github.com/glemmaPaul/django-stdimage-serializer/blob/master/stdimage_serializer/fields.py#L4).

I'd be happy to contribute this in the near future.


Discussion / hints:
@chgad excellent idea. Why not, it's such a common framework, let's make it happen.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1a30d25..c4d046c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -67,6 +67,10 @@ jobs:
             python-version: "3.10"
             django-version: "4.0"
             extras: "test,celery"
+          - os: "ubuntu-latest"
+            python-version: "3.10"
+            django-version: "4.0"
+            extras: "test,drf"
 
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/README.md b/README.md
index 45e5c50..6140ded 100644
--- a/README.md
+++ b/README.md
@@ -135,3 +135,19 @@ You can simply auto create the migration and replace Django's
 You can follow [the example][migration] in our test app, to see how it works.
 
 [migration]: tests/testapp/migrations/0002_alter_profile_picture.py
+
+
+## Contrib
+
+### Django Rest Framework (DRF)
+
+We do ship with a read-only `PictureField` that can be used to include all
+available picture sizes in a DRF serializer.
+
+```python
+from rest_framework import serializers
+from pictures.contrib.rest_framework import PictureField
+
+class PictureSerializer(serializers.Serializer):
+    picture = PictureField()
+```
diff --git a/pictures/contrib/__init__.py b/pictures/contrib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pictures/contrib/rest_framework.py b/pictures/contrib/rest_framework.py
new file mode 100644
index 0000000..b368a5e
--- /dev/null
+++ b/pictures/contrib/rest_framework.py
@@ -0,0 +1,20 @@
+import json
+
+from rest_framework import serializers
+
+__all__ = ["PictureField"]
+
+from pictures.models import PictureFieldFile, SimplePicture
+
+
+def default(obj):
+    if isinstance(obj, SimplePicture):
+        return obj.url
+    raise TypeError(f"Type '{type(obj).__name__}' not serializable")
+
+
+class PictureField(serializers.ReadOnlyField):
+    """Read-only field for all aspect ratios and sizes of the image."""
+
+    def to_representation(self, obj: PictureFieldFile):
+        return json.loads(json.dumps(obj.aspect_ratios, default=default))
diff --git a/pyproject.toml b/pyproject.toml
index dff8894..709317a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -44,6 +44,9 @@ dramatiq = [
 celery = [
   "celery",
 ]
+drf = [
+  "djangorestframework",
+]
 
 [project.urls]
 Project-URL = "https://github.com/codingjoe/django-pictures"


Test patch:
diff --git a/tests/contrib/__init__.py b/tests/contrib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/contrib/test_rest_framework.py b/tests/contrib/test_rest_framework.py
new file mode 100644
index 0000000..9cdc326
--- /dev/null
+++ b/tests/contrib/test_rest_framework.py
@@ -0,0 +1,97 @@
+from fractions import Fraction
+
+import pytest
+from django.core.files.storage import default_storage
+
+from pictures.models import SimplePicture
+from tests.testapp import models
+
+serializers = pytest.importorskip("rest_framework.serializers")
+rest_framework = pytest.importorskip("pictures.contrib.rest_framework")
+
+
+class ProfileSerializer(serializers.ModelSerializer):
+    picture = rest_framework.PictureField()
+
+    class Meta:
+        model = models.Profile
+        fields = ["picture"]
+
+
+def test_default():
+    assert (
+        rest_framework.default(
+            obj=SimplePicture(
+                parent_name="testapp/simplemodel/image.jpg",
+                file_type="WEBP",
+                aspect_ratio=Fraction("4/3"),
+                storage=default_storage,
+                width=800,
+            )
+        )
+        == "/media/testapp/simplemodel/image/4_3/800w.webp"
+    )
+
+
+def test_default__type_error():
+    with pytest.raises(TypeError) as e:
+        rest_framework.default("not a picture")
+    assert str(e.value) == "Type 'str' not serializable"
+
+
+class TestPictureField:
+    @pytest.mark.django_db
+    def test_to_representation(self, image_upload_file):
+
+        profile = models.Profile.objects.create(picture=image_upload_file)
+        serializer = ProfileSerializer(profile)
+        assert serializer.data["picture"] == {
+            "null": {
+                "WEBP": {
+                    "800": "/media/testapp/profile/image/800w.webp",
+                    "100": "/media/testapp/profile/image/100w.webp",
+                    "200": "/media/testapp/profile/image/200w.webp",
+                    "300": "/media/testapp/profile/image/300w.webp",
+                    "400": "/media/testapp/profile/image/400w.webp",
+                    "500": "/media/testapp/profile/image/500w.webp",
+                    "600": "/media/testapp/profile/image/600w.webp",
+                    "700": "/media/testapp/profile/image/700w.webp",
+                }
+            },
+            "1/1": {
+                "WEBP": {
+                    "800": "/media/testapp/profile/image/1/800w.webp",
+                    "100": "/media/testapp/profile/image/1/100w.webp",
+                    "200": "/media/testapp/profile/image/1/200w.webp",
+                    "300": "/media/testapp/profile/image/1/300w.webp",
+                    "400": "/media/testapp/profile/image/1/400w.webp",
+                    "500": "/media/testapp/profile/image/1/500w.webp",
+                    "600": "/media/testapp/profile/image/1/600w.webp",
+                    "700": "/media/testapp/profile/image/1/700w.webp",
+                }
+            },
+            "3/2": {
+                "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",
+                }
+            },
+            "16/9": {
+                "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",
+                }
+            },
+        }