12

i'm doing an upgrade to DRF3.1.1 from 2.4. I was using a custom serializer to create an instance of an object that's not a Model.

In 2.4, it was easy enough to do this because in the serializer, I would create the object in restore_object(). In the view, i'd call serializer.is_valid() and then pop the instance of the object out of the serializer with serializer.object. Then I could do whatever I want.

With the 3.x changes, it's harder to get the instance out of the object because the create and update methods are supposed to do the saving, and "serializer.object" isn't available anymore.

As an example, I used to have this for my "UserRegistration" Object. This is not a model because it's a convenience object that the server parses up and stores data in a number of other objects/db tables.

class UserRegistration(object):
    def __init__(self, full_name, stage_name, password="", email="", locale="en_US"):
        self.full_name = full_name
        self.password = password
        self.locale = locale
        self.email = email
        self.stage_name = stage_name

Here's the associated DRF-2.4 serializer:

class UserRegistrationSerializer(serializers.Serializer):
    full_name = serializers.CharField(max_length=128, required=False)
    stage_name = serializers.CharField(max_length=128)
    password = serializers.CharField(max_length=128, required=False)
    locale = serializers.CharField(max_length=10, required=False)
    # use CharField instead of EmailField for email. We do our own validation later to make for a better error msg.
    email = serializers.CharField(max_length=254, required=False)

    def restore_object(self, attrs, instance=None):
        if instance is not None:
            instance.full_name = attrs.get('full_name', instance.full_name)
            instance.password = attrs.get('password', instance.password)
            instance.locale = attrs.get('locale', instance.locale)
            instance.email = attrs.get('email', instance.email)
            instance.stage_name = attrs.get('stage_name', instance.stage_name)
            return instance
        return UserRegistration(**attrs)

Then in my view, I do something like this:

class UserRegistration(APIView):
    throttle_classes = ()
    serializer_class = UserRegistrationSerializer

    def post(self, request, format=None):
        event_type = "user_registration"
        serializer = UserRegistrationSerializer(data=request.DATA, context={'request': request})
        try:
            if serializer.is_valid():
            user_registration = serializer.object
            # save user_registration pieces in various places...

However, in DRF3, I serializer.object is gone. The docs say to do "validation" using serializer.validated_data, but that's just a hash and not the real object. Is there a way to get the object?

The whole thing seems more married to DB objects, which in this particular case is exactly what i'm trying to avoid.

Am I just missing some new DRF3 concept?

2 Answers 2

13

Thanks @levi for the beginnings of an answer, but unfortunately, that's not all of it, so I think this is a more complete answer.

I originally asked:

Am I just missing some new DRF3 concept?

Turns out...Yep. I was. The docs talk about the new Single-step object creation, which made me think the serialization and model had become more tightly coupled. This thought was incorrect, because if you write your own custom serializer, it's up to you to do the actual object save (or not) in the new serializer.update() and serializer.create() methods.

I also asked:

In 2.4, it was easy enough to do this because in the serializer, I would create the object in restore_object(). In the view, i'd call serializer.is_valid() and then pop the instance of the object out of the serializer with serializer.object. Then I could do whatever I want.

With the 3.x changes, it's harder to get the instance out of the object because the create and update methods are supposed to do the saving, and "serializer.object" isn't available anymore.

Although there's no serializer.object that you can use to pull the created object out of after calling serializer.is_valid(), the serializer.save() method returns the object itself, which in my case was just fine.

So, turns out, the code change wasn't very big at all. Here's my new code that is pretty happy with DRF-3:

class UserRegistration(object):
    def __init__(self, full_name, stage_name, password="", email="", locale="en_US", notification_pref="ask"):
        self.full_name = full_name
        self.password = password
        self.locale = locale
        self.email = email
        self.stage_name = stage_name


class UserRegistrationSerializer(serializers.Serializer):
    full_name = serializers.CharField(max_length=128, required=False)
    stage_name = serializers.CharField(max_length=128)
    password = serializers.CharField(max_length=128, required=False)
    locale = serializers.CharField(max_length=10, required=False)
    # use CharField instead of EmailField for email. We do our own validation later to make for a better error msg.
    email = serializers.CharField(max_length=254, required=False)

    def update(self, instance, validated_data):
        instance.full_name = validated_data.get('full_name', instance.full_name)
        instance.password = validated_data.get('password', instance.password)
        instance.locale = validated_data.get('locale', instance.locale)
        instance.email = validated_data.get('email', instance.email)
        instance.stage_name = validated_data.get('stage_name', instance.stage_name)
        return instance

    def create(self, validated_data):
        return UserRegistration(**validated_data)

notice that there's no saving of the object to any DB in the Serializer. I'm just creating or updating the object and then returning it.

Now the view looks like this:

class UserRegistration(APIView):
    throttle_classes = ()
    serializer_class = UserRegistrationSerializer

    def post(self, request, format=None):
        event_type = "user_registration"
        serializer = UserRegistrationSerializer(data=request.DATA, context={'request': request})
        try:
            if serializer.is_valid():
                user_registration = serializer.save()
                # save user_registration pieces in various places...

I also said in my original post:

The whole thing seems more married to DB objects, which in this particular case is exactly what i'm trying to avoid.

This statement was also incorrect as seen by the fact that the create and update methods don't have to save anything to any DB.

One caveat here is that the code is functional, but obviously I'm just wrapping my head around some of the DRF2.x->3.x changes, so I could be doing this in a non-DRF way. If so, someone who knows please feel free to tell me how to do it better. :)

Sign up to request clarification or add additional context in comments.

6 Comments

as a heads up you should never use a str as a default value for a function as it becomes mutable and can lead to strange behavior -- docs.python-guide.org/en/latest/writing/gotchas
@Alvin What you are saying is true for mutable types but I'm almost 100% certain python strings are immutable.
Just a quick note: 'request.DATA' has been deprecated in favor of 'request.data' since version 3.0, and has been fully removed as of version 3.2.
DRF serializers don't fit well with non-model Classes. They provide no validation at all, hence I cannot see why to use one.
BTW--Python strings ARE mutable, so beware.
|
1

Yes, you can get the object itself using DRF 3. Your update method should have this signature update(self, instance, validated_data)

Your serializer should looks like this:

class UserRegistrationSerializer(serializers.Serializer):
    full_name = serializers.CharField(max_length=128, required=False)
    stage_name = serializers.CharField(max_length=128)
    password = serializers.CharField(max_length=128, required=False)
    locale = serializers.CharField(max_length=10, required=False)
    # use CharField instead of EmailField for email. We do our own validation later to make for a better error msg.
    email = serializers.CharField(max_length=254, required=False)

    def update(self, instance, validated_data):
          // here instance is the object . 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.