drf-writable-nested


Namedrf-writable-nested JSON
Version 0.7.0 PyPI version JSON
download
home_pagehttp://github.com/beda-software/drf-writable-nested
SummaryWritable nested helpers for django-rest-framework's serializers
upload_time2022-08-11 10:16:06
maintainer
docs_urlNone
authorbeda.software
requires_python>=3.7
licenseBSD
keywords drf restframework rest_framework django_rest_framework serializers drf_writable_nested
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            DRF Writable Nested
====================
![build](https://github.com/beda-software/drf-writable-nested/actions/workflows/build.yaml/badge.svg)
[![codecov](https://codecov.io/gh/beda-software/drf-writable-nested/branch/master/graph/badge.svg)](https://codecov.io/gh/beda-software/drf-writable-nested)
[![pypi](https://img.shields.io/pypi/v/drf-writable-nested.svg)](https://pypi.python.org/pypi/drf-writable-nested)
[![pyversions](https://img.shields.io/pypi/pyversions/drf-writable-nested.svg)](https://pypi.python.org/pypi/drf-writable-nested)

This is a writable nested model serializer for Django REST Framework which
allows you to create/update your models with related nested data.

The following relations are supported:
- OneToOne (direct/reverse)
- ForeignKey (direct/reverse)
- ManyToMany (direct/reverse excluding m2m relations with through model)
- GenericRelation (this is always only reverse)

Requirements
============

- Python (3.5, 3.6, 3.7, 3.8, 3.9, 3.10)
- Django (2.2, 3.0, 3.1, 3.2, 4.0)
- djangorestframework (3.8+)

Installation
============

```
pip install drf-writable-nested
```

Usage
=====

For example, for the following model structure:
```python
from django.db import models


class Site(models.Model):
    url = models.CharField(max_length=100)


class User(models.Model):
    username = models.CharField(max_length=100)


class AccessKey(models.Model):
    key = models.CharField(max_length=100)


class Profile(models.Model):
    sites = models.ManyToManyField(Site)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    access_key = models.ForeignKey(AccessKey, null=True, on_delete=models.CASCADE)


class Avatar(models.Model):
    image = models.CharField(max_length=100)
    profile = models.ForeignKey(Profile, related_name='avatars', on_delete=models.CASCADE)
```

We should create the following list of serializers:

```python
from rest_framework import serializers
from drf_writable_nested.serializers import WritableNestedModelSerializer


class AvatarSerializer(serializers.ModelSerializer):
    image = serializers.CharField()

    class Meta:
        model = Avatar
        fields = ('pk', 'image',)


class SiteSerializer(serializers.ModelSerializer):
    url = serializers.CharField()

    class Meta:
        model = Site
        fields = ('pk', 'url',)


class AccessKeySerializer(serializers.ModelSerializer):

    class Meta:
        model = AccessKey
        fields = ('pk', 'key',)


class ProfileSerializer(WritableNestedModelSerializer):
    # Direct ManyToMany relation
    sites = SiteSerializer(many=True)

    # Reverse FK relation
    avatars = AvatarSerializer(many=True)

    # Direct FK relation
    access_key = AccessKeySerializer(allow_null=True)

    class Meta:
        model = Profile
        fields = ('pk', 'sites', 'avatars', 'access_key',)


class UserSerializer(WritableNestedModelSerializer):
    # Reverse OneToOne relation
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ('pk', 'profile', 'username',)
```

Also, you can use `NestedCreateMixin` or `NestedUpdateMixin` from this package
if you want to support only create or update logic.

For example, we can pass the following data with related nested fields to our
main serializer:

```python
data = {
    'username': 'test',
    'profile': {
        'access_key': {
            'key': 'key',
        },
        'sites': [
            {
                'url': 'http://google.com',
            },
            {
                'url': 'http://yahoo.com',
            },
        ],
        'avatars': [
            {
                'image': 'image-1.png',
            },
            {
                'image': 'image-2.png',
            },
        ],
    },
}

user_serializer = UserSerializer(data=data)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()
```

This serializer will automatically create all nested relations and we receive a
complete instance with filled data.
```python
user_serializer = UserSerializer(instance=user)
print(user_serializer.data)
```

```python
{
    'pk': 1,
    'username': 'test',
    'profile': {
        'pk': 1,
        'access_key': {
            'pk': 1,
            'key': 'key'
        },
        'sites': [
            {
                'pk': 1,
                'url': 'http://google.com',
            },
            {
                'pk': 2,
                'url': 'http://yahoo.com',
            },
        ],
        'avatars': [
            {
                'pk': 1,
                'image': 'image-1.png',
            },
            {
                'pk': 2,
                'image': 'image-2.png',
            },
        ],
    },
}
```

It is also possible to pass through values to nested serializers from the call
to the base serializer's `save` method. These `kwargs` must be of type `dict`. E g:

```python
# user_serializer created with 'data' as above
user = user_serializer.save(
    profile={
        'access_key': {'key': 'key2'},
    },
)
print(user.profile.access_key.key)
```

```python
'key2'
```

Note: The same value will be used for all nested instances like default value but with higher priority.


Testing
=======
To run unit tests, run:
```bash
# Setup the virtual environment
python3 -m venv envname
source envname/bin/activate

pip install django
pip install django-rest-framework
pip install -r requirements.txt

# Run tests
py.test
```


Known problems with solutions
=============================


##### Validation problem for nested serializers with unique fields on update
We have a special mixin `UniqueFieldsMixin` which solves this problem.
The mixin moves` UniqueValidator`'s from the validation stage to the save stage.

If you want more details, you can read related issues and articles:
https://github.com/beda-software/drf-writable-nested/issues/1
http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers

###### Example of usage:
```python
class Child(models.Model):
    field = models.CharField(unique=True)


class Parent(models.Model):
    child = models.ForeignKey('Child')


class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
    class Meta:
        model = Child


class ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):
    child = ChildSerializer()

    class Meta:
        model = Parent
```

Note: `UniqueFieldsMixin` must be applied only on serializer
which has unique fields.

###### Mixin ordering
When you are using both mixins
(`UniqueFieldsMixin` and `NestedCreateMixin` or `NestedUpdateMixin`)
you should put `UniqueFieldsMixin` ahead.

For example:
```python
class ChildSerializer(UniqueFieldsMixin, NestedUpdateMixin,
        serializers.ModelSerializer):
```

##### Update problem for nested fields with form-data in `PATCH` and `PUT` methods
There is a special problem while we try to update any model object with nested fields
within it via `PUT` or `PATCH` using form-data we can not update it. And it complains
about fields not provided. So far, we came to know that this is also a problem in DRF.
But we can follow a tricky way to solve it at least for now.
See the below solution about the problem

If you want more details, you can read related issues and articles:
https://github.com/beda-software/drf-writable-nested/issues/106
https://github.com/encode/django-rest-framework/issues/7262#issuecomment-737364846

###### Example:
```python

# Models
class Voucher(models.Model):
    voucher_number = models.CharField(verbose_name="voucher number", max_length=10, default='')
    image = models.ImageField(upload_to="vouchers/images/", null=True, blank=True)

class VoucherRow(models.Model):
    voucher = models.ForeignKey(to='voucher.Voucher', on_delete=models.PROTECT, verbose_name='voucher',
                                related_name='voucherrows', null=True)
    account = models.CharField(verbose_name="fortnox account number", max_length=255)
    debit = models.DecimalField(verbose_name="amount", decimal_places=2, default=0.00, max_digits=12)
    credit = models.DecimalField(verbose_name="amount", decimal_places=2, default=0.00, max_digits=12)
    description = models.CharField(verbose_name="description", max_length=100, null=True, blank=True)

# Serializers for these models
class VoucherRowSerializer(WritableNestedModelSerializer):
    class Meta:
        model = VoucherRow
        fields = ('id', 'account', 'debit', 'credit', 'description',)


class VoucherSerializer(serializers.ModelSerializer):
    voucherrows = VoucherRowSerializer(many=True, required=False, read_only=True)
    class Meta:
        model = Voucher
        fields = ('id', 'participants', 'voucher_number', 'voucherrows', 'image')

```

Now if you want to update `Voucher` with `VoucherRow` and voucher image then you need to do it
using form-data via `PUT` or `PATCH` request where your `voucherrows` fields are nested field.
With the current implementation of the `drf-writable-nested` doesn't update it. Because it does
not support something like-

```text
voucherrows[1].account=1120
voucherrows[1].debit=1000.00
voucherrows[1].credit=0.00
voucherrows[1].description='Debited from Bank Account' 
voucherrows[2].account=1130
voucherrows[2].debit=0.00
voucherrows[2].credit=1000.00
voucherrows[2].description='Credited to Cash Account'

```
This is not supported at least for now. So, we can achieve the result in a different way.
Instead of sending the array fields separately in this way we can convert the whole fields
along with values in a `json` string like below and set it as value to the field `voucherrows`.

```json
"[{\"account\": 1120, \"debit\": 1000.00, \"credit\": 0.00, \"description\": \"Debited from Bank Account\"}, {\"account\": 1130, \"debit\": 0.00, \"credit\": 1000.00, \"description\": \"Credited to Cash Account\"}]"
```

Now it'll be actually sent as a single field value to the application for the field `voucherrows`.
From your `views` you need to parse it like below before sending it to the serializer-

```python
class VoucherViewSet(viewsets.ModelViewSet):
    serializer_class = VoucherSerializer
    queryset = serializer_class.Meta.model.objects.all().order_by('-created_at')
    
    def update(self, request, *args, **kwargs):
        request.data.update({'voucherrows': json.loads(request.data.pop('voucherrows', None))})
        return super().update(request, *args, **kwargs)
```
Now, you'll get the `voucherrows` field with data in the right format in your serializers.
Similar approach will be also applicable for generic views for django rest framework

Authors
=======
2014-2022, beda.software

            

Raw data

            {
    "_id": null,
    "home_page": "http://github.com/beda-software/drf-writable-nested",
    "name": "drf-writable-nested",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "drf restframework rest_framework django_rest_framework serializers drf_writable_nested",
    "author": "beda.software",
    "author_email": "drfwritablenested@beda.software",
    "download_url": "",
    "platform": null,
    "description": "DRF Writable Nested\n====================\n![build](https://github.com/beda-software/drf-writable-nested/actions/workflows/build.yaml/badge.svg)\n[![codecov](https://codecov.io/gh/beda-software/drf-writable-nested/branch/master/graph/badge.svg)](https://codecov.io/gh/beda-software/drf-writable-nested)\n[![pypi](https://img.shields.io/pypi/v/drf-writable-nested.svg)](https://pypi.python.org/pypi/drf-writable-nested)\n[![pyversions](https://img.shields.io/pypi/pyversions/drf-writable-nested.svg)](https://pypi.python.org/pypi/drf-writable-nested)\n\nThis is a writable nested model serializer for Django REST Framework which\nallows you to create/update your models with related nested data.\n\nThe following relations are supported:\n- OneToOne (direct/reverse)\n- ForeignKey (direct/reverse)\n- ManyToMany (direct/reverse excluding m2m relations with through model)\n- GenericRelation (this is always only reverse)\n\nRequirements\n============\n\n- Python (3.5, 3.6, 3.7, 3.8, 3.9, 3.10)\n- Django (2.2, 3.0, 3.1, 3.2, 4.0)\n- djangorestframework (3.8+)\n\nInstallation\n============\n\n```\npip install drf-writable-nested\n```\n\nUsage\n=====\n\nFor example, for the following model structure:\n```python\nfrom django.db import models\n\n\nclass Site(models.Model):\n    url = models.CharField(max_length=100)\n\n\nclass User(models.Model):\n    username = models.CharField(max_length=100)\n\n\nclass AccessKey(models.Model):\n    key = models.CharField(max_length=100)\n\n\nclass Profile(models.Model):\n    sites = models.ManyToManyField(Site)\n    user = models.OneToOneField(User, on_delete=models.CASCADE)\n    access_key = models.ForeignKey(AccessKey, null=True, on_delete=models.CASCADE)\n\n\nclass Avatar(models.Model):\n    image = models.CharField(max_length=100)\n    profile = models.ForeignKey(Profile, related_name='avatars', on_delete=models.CASCADE)\n```\n\nWe should create the following list of serializers:\n\n```python\nfrom rest_framework import serializers\nfrom drf_writable_nested.serializers import WritableNestedModelSerializer\n\n\nclass AvatarSerializer(serializers.ModelSerializer):\n    image = serializers.CharField()\n\n    class Meta:\n        model = Avatar\n        fields = ('pk', 'image',)\n\n\nclass SiteSerializer(serializers.ModelSerializer):\n    url = serializers.CharField()\n\n    class Meta:\n        model = Site\n        fields = ('pk', 'url',)\n\n\nclass AccessKeySerializer(serializers.ModelSerializer):\n\n    class Meta:\n        model = AccessKey\n        fields = ('pk', 'key',)\n\n\nclass ProfileSerializer(WritableNestedModelSerializer):\n    # Direct ManyToMany relation\n    sites = SiteSerializer(many=True)\n\n    # Reverse FK relation\n    avatars = AvatarSerializer(many=True)\n\n    # Direct FK relation\n    access_key = AccessKeySerializer(allow_null=True)\n\n    class Meta:\n        model = Profile\n        fields = ('pk', 'sites', 'avatars', 'access_key',)\n\n\nclass UserSerializer(WritableNestedModelSerializer):\n    # Reverse OneToOne relation\n    profile = ProfileSerializer()\n\n    class Meta:\n        model = User\n        fields = ('pk', 'profile', 'username',)\n```\n\nAlso, you can use `NestedCreateMixin` or `NestedUpdateMixin` from this package\nif you want to support only create or update logic.\n\nFor example, we can pass the following data with related nested fields to our\nmain serializer:\n\n```python\ndata = {\n    'username': 'test',\n    'profile': {\n        'access_key': {\n            'key': 'key',\n        },\n        'sites': [\n            {\n                'url': 'http://google.com',\n            },\n            {\n                'url': 'http://yahoo.com',\n            },\n        ],\n        'avatars': [\n            {\n                'image': 'image-1.png',\n            },\n            {\n                'image': 'image-2.png',\n            },\n        ],\n    },\n}\n\nuser_serializer = UserSerializer(data=data)\nuser_serializer.is_valid(raise_exception=True)\nuser = user_serializer.save()\n```\n\nThis serializer will automatically create all nested relations and we receive a\ncomplete instance with filled data.\n```python\nuser_serializer = UserSerializer(instance=user)\nprint(user_serializer.data)\n```\n\n```python\n{\n    'pk': 1,\n    'username': 'test',\n    'profile': {\n        'pk': 1,\n        'access_key': {\n            'pk': 1,\n            'key': 'key'\n        },\n        'sites': [\n            {\n                'pk': 1,\n                'url': 'http://google.com',\n            },\n            {\n                'pk': 2,\n                'url': 'http://yahoo.com',\n            },\n        ],\n        'avatars': [\n            {\n                'pk': 1,\n                'image': 'image-1.png',\n            },\n            {\n                'pk': 2,\n                'image': 'image-2.png',\n            },\n        ],\n    },\n}\n```\n\nIt is also possible to pass through values to nested serializers from the call\nto the base serializer's `save` method. These `kwargs` must be of type `dict`. E g:\n\n```python\n# user_serializer created with 'data' as above\nuser = user_serializer.save(\n    profile={\n        'access_key': {'key': 'key2'},\n    },\n)\nprint(user.profile.access_key.key)\n```\n\n```python\n'key2'\n```\n\nNote: The same value will be used for all nested instances like default value but with higher priority.\n\n\nTesting\n=======\nTo run unit tests, run:\n```bash\n# Setup the virtual environment\npython3 -m venv envname\nsource envname/bin/activate\n\npip install django\npip install django-rest-framework\npip install -r requirements.txt\n\n# Run tests\npy.test\n```\n\n\nKnown problems with solutions\n=============================\n\n\n##### Validation problem for nested serializers with unique fields on update\nWe have a special mixin `UniqueFieldsMixin` which solves this problem.\nThe mixin moves` UniqueValidator`'s from the validation stage to the save stage.\n\nIf you want more details, you can read related issues and articles:\nhttps://github.com/beda-software/drf-writable-nested/issues/1\nhttp://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers\n\n###### Example of usage:\n```python\nclass Child(models.Model):\n    field = models.CharField(unique=True)\n\n\nclass Parent(models.Model):\n    child = models.ForeignKey('Child')\n\n\nclass ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):\n    class Meta:\n        model = Child\n\n\nclass ParentSerializer(NestedUpdateMixin, serializers.ModelSerializer):\n    child = ChildSerializer()\n\n    class Meta:\n        model = Parent\n```\n\nNote: `UniqueFieldsMixin` must be applied only on serializer\nwhich has unique fields.\n\n###### Mixin ordering\nWhen you are using both mixins\n(`UniqueFieldsMixin` and `NestedCreateMixin` or `NestedUpdateMixin`)\nyou should put `UniqueFieldsMixin` ahead.\n\nFor example:\n```python\nclass ChildSerializer(UniqueFieldsMixin, NestedUpdateMixin,\n        serializers.ModelSerializer):\n```\n\n##### Update problem for nested fields with form-data in `PATCH` and `PUT` methods\nThere is a special problem while we try to update any model object with nested fields\nwithin it via `PUT` or `PATCH` using form-data we can not update it. And it complains\nabout fields not provided. So far, we came to know that this is also a problem in DRF.\nBut we can follow a tricky way to solve it at least for now.\nSee the below solution about the problem\n\nIf you want more details, you can read related issues and articles:\nhttps://github.com/beda-software/drf-writable-nested/issues/106\nhttps://github.com/encode/django-rest-framework/issues/7262#issuecomment-737364846\n\n###### Example:\n```python\n\n# Models\nclass Voucher(models.Model):\n    voucher_number = models.CharField(verbose_name=\"voucher number\", max_length=10, default='')\n    image = models.ImageField(upload_to=\"vouchers/images/\", null=True, blank=True)\n\nclass VoucherRow(models.Model):\n    voucher = models.ForeignKey(to='voucher.Voucher', on_delete=models.PROTECT, verbose_name='voucher',\n                                related_name='voucherrows', null=True)\n    account = models.CharField(verbose_name=\"fortnox account number\", max_length=255)\n    debit = models.DecimalField(verbose_name=\"amount\", decimal_places=2, default=0.00, max_digits=12)\n    credit = models.DecimalField(verbose_name=\"amount\", decimal_places=2, default=0.00, max_digits=12)\n    description = models.CharField(verbose_name=\"description\", max_length=100, null=True, blank=True)\n\n# Serializers for these models\nclass VoucherRowSerializer(WritableNestedModelSerializer):\n    class Meta:\n        model = VoucherRow\n        fields = ('id', 'account', 'debit', 'credit', 'description',)\n\n\nclass VoucherSerializer(serializers.ModelSerializer):\n    voucherrows = VoucherRowSerializer(many=True, required=False, read_only=True)\n    class Meta:\n        model = Voucher\n        fields = ('id', 'participants', 'voucher_number', 'voucherrows', 'image')\n\n```\n\nNow if you want to update `Voucher` with `VoucherRow` and voucher image then you need to do it\nusing form-data via `PUT` or `PATCH` request where your `voucherrows` fields are nested field.\nWith the current implementation of the `drf-writable-nested` doesn't update it. Because it does\nnot support something like-\n\n```text\nvoucherrows[1].account=1120\nvoucherrows[1].debit=1000.00\nvoucherrows[1].credit=0.00\nvoucherrows[1].description='Debited from Bank Account' \nvoucherrows[2].account=1130\nvoucherrows[2].debit=0.00\nvoucherrows[2].credit=1000.00\nvoucherrows[2].description='Credited to Cash Account'\n\n```\nThis is not supported at least for now. So, we can achieve the result in a different way.\nInstead of sending the array fields separately in this way we can convert the whole fields\nalong with values in a `json` string like below and set it as value to the field `voucherrows`.\n\n```json\n\"[{\\\"account\\\": 1120, \\\"debit\\\": 1000.00, \\\"credit\\\": 0.00, \\\"description\\\": \\\"Debited from Bank Account\\\"}, {\\\"account\\\": 1130, \\\"debit\\\": 0.00, \\\"credit\\\": 1000.00, \\\"description\\\": \\\"Credited to Cash Account\\\"}]\"\n```\n\nNow it'll be actually sent as a single field value to the application for the field `voucherrows`.\nFrom your `views` you need to parse it like below before sending it to the serializer-\n\n```python\nclass VoucherViewSet(viewsets.ModelViewSet):\n    serializer_class = VoucherSerializer\n    queryset = serializer_class.Meta.model.objects.all().order_by('-created_at')\n    \n    def update(self, request, *args, **kwargs):\n        request.data.update({'voucherrows': json.loads(request.data.pop('voucherrows', None))})\n        return super().update(request, *args, **kwargs)\n```\nNow, you'll get the `voucherrows` field with data in the right format in your serializers.\nSimilar approach will be also applicable for generic views for django rest framework\n\nAuthors\n=======\n2014-2022, beda.software\n",
    "bugtrack_url": null,
    "license": "BSD",
    "summary": "Writable nested helpers for django-rest-framework's serializers",
    "version": "0.7.0",
    "project_urls": {
        "Homepage": "http://github.com/beda-software/drf-writable-nested"
    },
    "split_keywords": [
        "drf",
        "restframework",
        "rest_framework",
        "django_rest_framework",
        "serializers",
        "drf_writable_nested"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "acc50e5c2a315829fdfe0fcdadffa68fc07dc65a5a82d3ef146df56d89ad16d1",
                "md5": "2a5d4203a1f0ecbbdaceb59929c5aafa",
                "sha256": "154c0381e8a3a477e0fd539d5e1caf8ff4c1097a9c0c0fe741d4858b11b0455b"
            },
            "downloads": -1,
            "filename": "drf_writable_nested-0.7.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2a5d4203a1f0ecbbdaceb59929c5aafa",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 10564,
            "upload_time": "2022-08-11T10:16:06",
            "upload_time_iso_8601": "2022-08-11T10:16:06.219912Z",
            "url": "https://files.pythonhosted.org/packages/ac/c5/0e5c2a315829fdfe0fcdadffa68fc07dc65a5a82d3ef146df56d89ad16d1/drf_writable_nested-0.7.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2022-08-11 10:16:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "beda-software",
    "github_project": "drf-writable-nested",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "tox": true,
    "lcname": "drf-writable-nested"
}
        
Elapsed time: 0.09062s