1

I use mongoengine with django rest framework. My model:

import mongoengine as mongo
class Plan(mongo.Document):
    slug = mongo.StringField(max_length=255, primary_key=True)
    subplans = mongo.ListField(mongo.EmbeddedDocumentField('self'))

I'm need serializer that be looks like this:

class PlanSerializer(serializers.DocumentSerializer):
    subplans = PlanSerializer(many=True, required=False)

    class Meta:
        model = Plan

But that incorrect for Python. So I use metaclass for adding subplans field dynamicly:

class AddSubplanAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        # this code is incorrect because PlanSerializer not in globals
        class_obj = globals()[name]
        dct['subplans'] = class_obj(many=True, required=False)
        return super(AddSubplanAttrMetaclass, cls).__new__(cls, name, bases, dct)

class PlanSerializer(serializers.DocumentSerializer, metaclass=AddSubplanAttrMetaclass):

    class Meta:
        model = Plan

How I can set PlanSerializer class to property inside __new__ method of metaclass?

Lariat
  • 167
  • 2
  • 11

1 Answers1

0

The problem you have there is that when you try to either use the line subplans = PlanSerializer(many=True, required=False) and when trying with the metaclass, the line class_obj = globals()[name] when your PlanSerializerclass itself was not defined yet. (Check my answer at How is super() in Python 3 implemented?)

The correct way to do that in the metaclass would be to call the superclass's new first - that returns you the actual class object, and then call that object - something along:

class AddSubplanAttrMetaclass(type):
    def __new__(metacls, name, bases, dct):
        # this code is incorrect because PlanSerializer not in globals
        class_obj = super(AddSubplanAttrMetaclass, cls).__new__(metacls, name, bases, dct)
        class_obj.subplans = class_obj(many=True, required=False)
        return class_obj

But that is both not needed, and might still have issues - as not all the class initialization is completed while you are still inside the metaclass's __new__ (or even __init__) methods. For example, if the __init__ method of PlanSerializer itself would make use of super, that call would fail - super can only be used after class has been fully initialized.

However, you don't need a metaclass at all for that - you probably can simply set the subplans attribute as a descriptor - and retrieve the attribute lazily.

class PlanSerializer(serializers.DocumentSerializer):
    class Meta:
        model = Plan

PlanSerializer.subplans = PlanSerializer(many=True, required=False)

I said probably because this won't work if Mongo needs the attribute to be set when initializing the class itself - if that is the case, you can try resorting to a descriptor object. A descriptor is simply an object that implements the __get__ method, like below. That is usually done with the @property decorator, but that would not work for class level attributes, which you need for this case.

class PlanSerializer(serializers.DocumentSerializer):
    class Subplans(object):
        serializer = None
        def __get__(self,  instance, owner):
            if not self.serializer:
                self.serializer = PlanSerializer(many=True, required=False)   
            return  self.serializer
    subplans = Subplans()

    class Meta:
        model = Plan

In that way the usage of the call to the Subplans class is delayed to when it is actually used, instead of the time of parsing the class body, and it should work.

Community
  • 1
  • 1
jsbueno
  • 99,910
  • 10
  • 151
  • 209