Skip to content

Commit

Permalink
Marshmallow serialization 🔥️
Browse files Browse the repository at this point in the history
  • Loading branch information
stevelacey committed Nov 15, 2021
1 parent e0090c0 commit 8cd0a80
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 191 deletions.
304 changes: 164 additions & 140 deletions Pipfile.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get_version(rel_path):
install_requires=[
"Django>=3.0.0,<3.3",
"django-url-filter>=0.3.15",
"marshmallow>=3.14.0",
],
include_package_data=True,
keywords="django, rest, framework, api",
Expand Down
19 changes: 0 additions & 19 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,6 @@ class Role(models.Model):
class Skill(models.Model):
name = models.CharField(max_length=200)

def api(self):
return dict(
id=self.pk,
name=self.name,
)


class RatedSkill(models.Model):
class Meta:
Expand All @@ -52,23 +46,10 @@ class Meta:
def __str__(self):
return f"{self.skill.name}: {self.get_rating_display()}"

def api(self):
return dict(
id=self.skill.pk,
name=self.skill.name,
rating=self.rating,
)


class Tag(models.Model):
name = models.CharField(max_length=200)

def api(self):
return dict(
id=self.pk,
name=self.name,
)


class Team(models.Model):
name = models.CharField(max_length=64)
Expand Down
65 changes: 42 additions & 23 deletions tests/serializers.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,56 @@
from worf.serializers import Serializer
from worf.serializers import fields, Serializer


class UserSerializer(Serializer):
def read(self, model):
return dict(
username=model.username,
lastLogin=model.last_login,
dateJoined=model.date_joined,
email=model.email,
)

def write(self):
return [
last_login = fields.DateTime(dump_only=True)
date_joined = fields.DateTime(dump_only=True)

class Meta:
fields = [
"username",
"last_login",
"date_joined",
"email",
]


class ProfileSerializer(Serializer):
def read(self, model):
return dict(
username=model.user.username,
email=model.user.email,
role=dict(name=model.role.name) if model.role else None,
skills=[t.api() for t in model.ratedskill_set.all()],
team=dict(name=model.team.name) if model.team else None,
tags=[t.api() for t in model.tags.all()],
)

def write(self):
return [
username = fields.Function(lambda obj: obj.user.username)
email = fields.Function(lambda obj: obj.user.email)
role = fields.Nested("RoleSerializer")
skills = fields.Nested("RatedSkillSerializer", attribute="ratedskill_set", many=True)
team = fields.Nested("TeamSerializer")
tags = fields.Nested("TagSerializer", many=True)

class Meta:
fields = [
"username",
"email",
"role",
"skills",
"team",
"tags",
]


class RatedSkillSerializer(Serializer):
id = fields.Function(lambda obj: obj.skill.id)
name = fields.Function(lambda obj: obj.skill.name)

class Meta:
fields = ["id", "name", "rating"]


class RoleSerializer(Serializer):
class Meta:
fields = ["id", "name"]


class TagSerializer(Serializer):
class Meta:
fields = ["id", "name"]


class TeamSerializer(Serializer):
class Meta:
fields = ["id", "name"]
11 changes: 11 additions & 0 deletions worf/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.db.models import Manager

import marshmallow.fields
from marshmallow.fields import * # noqa: F401, F403


class Nested(marshmallow.fields.Nested):
def _serialize(self, nested_obj, attr, obj, **kwargs):
if isinstance(nested_obj, Manager):
nested_obj = nested_obj.all()
return super()._serialize(nested_obj, attr, obj, **kwargs)
47 changes: 38 additions & 9 deletions worf/serializers.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,64 @@
import json
import marshmallow

from django.core.exceptions import ImproperlyConfigured

from worf import fields # noqa: F401
from worf.casing import snake_to_camel


def deserialize(response):
return json.loads(response.content.decode("UTF-8"))


class Serializer:
def create(self):
return []
class Serializer(marshmallow.Schema):
"""
Serializer base class with sensible defaults for typically read-only fields.
Worf serializers are basically marshmallow schemas with better support for
Django models, and some extra opinions around field naming and ordering.
The read/write methods are relics of our interim serialization strategy
they're analogous with marshmallow dump/load, but a bit less flexible.
Marshmallow is primarily used for field filtering right now, worf coerces
values itself, but we might delegated more to marshmallow later.
"""

id = fields.Function(lambda obj: obj.pk, dump_only=True)
created_at = fields.DateTime(dump_only=True)
deleted_at = fields.DateTime(dump_only=True)
updated_at = fields.DateTime(dump_only=True)

@property
def dict_class(self):
return dict

def list(self, items):
return [self.read(item) for item in items]

def read(self, model):
return {}
def on_bind_field(self, field_name, field_obj):
field_obj.data_key = snake_to_camel(field_obj.data_key or field_name)

def read(self, obj):
return self.dump(obj)

def write(self):
return []
return list(self.load_fields.keys())

class Meta:
ordered = True


class LegacySerializer(Serializer):
def __init__(self, model_class, api_method):
self.api_method = api_method
self.model_class = model_class

def read(self, model):
payload = getattr(model, self.api_method)()
def read(self, obj):
payload = getattr(obj, self.api_method)()
if not isinstance(payload, dict):
msg = f"{model.__name__}.{self.api_method}() did not return a dictionary"
msg = f"{obj.__name__}.{self.api_method}() did not return a dictionary"
raise ImproperlyConfigured(msg)
return payload

Expand Down

0 comments on commit 8cd0a80

Please sign in to comment.