• Stars
    star
    1,066
  • Rank 43,337 (Top 0.9 %)
  • Language
    Python
  • License
    Other
  • Created over 7 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Writable nested model serializer for Django REST Framework

DRF Writable Nested

build codecov pypi pyversions

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.7, 3.8, 3.9, 3.10, 3.11)
  • Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1, 4.2)
  • djangorestframework (3.8+)

Installation

pip install drf-writable-nested

Usage

For example, for the following model structure:

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:

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:

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.

user_serializer = UserSerializer(instance=user)
print(user_serializer.data)
{
    '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:

# user_serializer created with 'data' as above
user = user_serializer.save(
    profile={
        'access_key': {'key': 'key2'},
    },
)
print(user.profile.access_key.key)
'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:

# 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: #1 http://www.django-rest-framework.org/api-guide/validators/#updating-nested-serializers

Example of usage:
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:

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: #106 encode/django-rest-framework#7262 (comment)

Example:
# 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-

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.

"[{\"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-

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

More Repositories

1

fhir-py

FHIR Client for python
Python
167
star
2

fhir-emr

EMR based on FHIR
TypeScript
67
star
3

fhirpath-py

FHIRPath implementation in Python.
Python
57
star
4

sdc-ide

IDE for https://build.fhir.org/ig/HL7/sdc/
TypeScript
15
star
5

fhir-py-types

Convert FHIR StructureDefinition into Python type annotations
Python
15
star
6

webpack-polyfills-plugin

Fork from Sl1v3r/webpack-polyfills-plugin
JavaScript
12
star
7

vscode-fhirpath

VScode extension for syntax highlighting for fhirpath
12
star
8

balancer

Load balancer for docker based web applications
Python
10
star
9

aidbox-react

A set of TypeScript components and utilities to create react apps on top of AidBox
TypeScript
10
star
10

fhir-sdc

http://hl7.org/fhir/uv/sdc/ implementation with python
Python
10
star
11

kaitenzushi

The simple way to check the transformation of your FSH files into the FHIR JSON.
GLSL
8
star
12

fhir-mhealth

Beda EMR mHealth app for iOS
TypeScript
8
star
13

paas

This project is an implementation as MVP of PaaS system. It is built with ansible on top of docker container engine.
Python
6
star
14

modeltranslation-grappelli

django-modeltranslation with grappelli support
JavaScript
6
star
15

aidbox-py

Aidbox client for Aidbox FHIR server
Python
6
star
16

react-form-tools

Form validation and form component for React+Baobab cursors
JavaScript
6
star
17

tk3

Kubernetes based platform for jupiter with strong Github integration
Clojure
5
star
18

aidbox-py-old

Aidbox client for python
Python
5
star
19

frontend-beda-software-stack

Monorepo of frontend (web and mobile) template
TypeScript
4
star
20

epic_sandbox_devdays_2023

DevDays 2023: Let's Build! Fetch data from Epic using FHIR API: Challenges and solutions - Notebook
Jupyter Notebook
4
star
21

testscript-eval-py

Python
3
star
22

telemedicine-demo-mobile

TypeScript
3
star
23

FAQ

3
star
24

emr-documentation

JavaScript
3
star
25

flatblocks

React + Django flatblocks implementation
Python
3
star
26

fhir-questionnaire

A set of TypeScript components and utilities to work with Questionnaire and QuestionnaireResponse resources
TypeScript
3
star
27

baobab-react-resolver

Promise resolver for baobab powered applications.
JavaScript
3
star
28

devdays2020-sdc-demo

Shell
3
star
29

cookiecutter-beda-software-stack

Python
2
star
30

baobab-react-schemabranchmixin

SchemaBranchMixin based on baobab-react
JavaScript
2
star
31

cornsnake

FHIR server implemented in python on top of asyncio
Python
1
star
32

ig-publisher

Dockerized ig publisher and sushi
Dockerfile
1
star
33

fhir-datasequence

Python
1
star
34

ovz

OVZ
Python
1
star
35

ansible-deploy

Ruby
1
star