django rest framework nested fields with multiple

2019-04-05 19:06发布

问题:

This is django and django rest framework. I have 2 models: User and phone.

The 1st problem:

I want to be able to update user data(email) alongside phone data(phone numbers) in 1 single api update response. Phone number can be 0 or many. Well, like partial=True actually. If a user just want to update phone numbers, don't update email and vice versa.


Additional info: At the time of registering, phone is not included. Just basic user info (last name, first name, email, password). The phone can only get updated in the user profile form after registration is done. The user profile form is actually linking to multiple models, which is User and Phone

The 2nd problem:

How to serialize multiple phone_numbers and save to db?

class User(AbstractBaseUser):
    email = models.EmailField(unique=True, default='')
    USERNAME_FIELD = 'email'


class Phone(models.Model):
    phone_number = models.CharField(max_length=10)
    owner = models.ForeignKey(User)

--------------------------------------
class UserSerializer(serializers.ModelSerializer):
    phone_number = PhoneSerializer(required=False, many=True)

    class Meta:
        model = User
        fields = ('email, 'phone_number')


class PhoneSerializer(serializers.ModelSerializer):

     class Meta:
          Model = Phone
          fields = ('phone_number')

The html form would have plus sign at the phone number field to indicate a new phone number can be added. example is here

email : admin@admin.com
phone number: 23423432423
(add more)
phone number: 3423423423
(add more)
...

The expected json

{
'email': 'email@email.com',
'phone_number': '32432433223234'
}

or if many phone numbers are added

{
'email': 'email@email.com',
'phone_number': '32432433223234',
'phone_number': '324342322342323'
}

or maybe

{
'email': 'email@email.com',
'phone_number': ['32432433223234','324342322342323']
}

or maybe

{
'email': 'email@email.com',
'Phone': [{'id': 1, 'phone_number': '32432433223234'}, {'id': 2, 'phone_number': '324342322342323'}]
}

is this json possible to do? how can serializer and modelviewset do it? sorry I'm totally new to drf

回答1:

  1. Default version for any nested objects.

You need to add serializer create and update methods:

class UserSerializer(serializers.ModelSerializer):
    phones = PhoneSerializer(required=False, many=True)

    class Meta:
        model = User
        fields = ('email', 'phone_number')

    def create(self, validated_data):
        phones_data = validated_data.pop('phones', [])
        user = super(UserSerializer, self).create(validated_data)
        for phone_data in phones_data:
            user.phone_set.create(phone_number=phone_data['phone_number'])
        return user

    def update(self, instance, validated_data):
        phones_data = validated_data.pop('phones', [])
        user = super(UserSerializer, self).update(instance, validated_data)
        # delete old
        user.phone_set.exclude(phone__in=[p['phone_number'] for p in phones_data]).delete()
        # create new
        for phone_data in phones_data:
            user.phone_set.get_or_create(phone_number=phone_data['phone_number'])
        return user

Request for creation:

{"email": "test@example.com" "phones": [{"phone_number": 12}, {"phone_number": 123}]}

Request for update:

{"phones": [{"phone_number": 22}]}
  1. Optimization for current structure:
    • do not store phone as int field - better string.
    • accept phones as list of strings, use ListField (http://www.django-rest-framework.org/api-guide/fields/#listfield), handle creation and updation in serializer methods.

UPDATE

  1. phones_data = validated_data.pop('phones') -> phones_data = validated_data.pop('phones', []), to handle case if there are no phones in request.

  2. Should phone updates and creation be done in modelviewset?

    No, serializer is responsible of transformation native data -> internal objects. So if it receive phones data - it should create PhoneNumber objects.