django-rest-framework-mongoengine icon indicating copy to clipboard operation
django-rest-framework-mongoengine copied to clipboard

KeyError on nested serializer

Open ShipraShalini opened this issue 8 years ago • 11 comments

I'm trying to create a Writable nested serializer as metioned in DRF Documentation.

I created following Models and serializers and am using dummy data to test it but it throws KeyError.

Models:

class User(Document):
    FirstName = StringField()
    LastName = StringField()
    Email = EmailField()
    Country = StringField()
    City = StringField()
    ProfilePic = URLField()


class Contact(Document):
    PresentAddress = StringField()
    PermanentAddress = StringField()
    PhoneNumber = StringField()


class ContactSerializer(DocumentSerializer):
    class Meta:
        model = Contact
        depth = 2

class UserSerializer(DocumentSerializer):
    ContactDetail=ContactSerializer()

    class Meta:
        model = User
        depth = 2

data:

{
    "FirstName": "eamczj",
    "LastName": "nrbifaxu",
    "Country": "Mexico",
    "Email": "[email protected]",
    "ContactDetail": {
        "PresentAddress":"nbirnbienibei",
        "PermanentAddress":"fbfwubfuwbgw",
        "PhoneNumber":"9862537341"
    }
}

KeyError: u"Got KeyError when attempting to get a value for field ContactDetail on serializer UserSerializer.\nThe serializer field might be named incorrectly and not match any attribute or key on the dict instance.\nOriginal exception text was: u'ContactDetail'."

ShipraShalini avatar Aug 22 '16 15:08 ShipraShalini

Hi, Shipra! In Mongonengine paradigm you don't need to create a separate collection for storing ContactDetails - it is an EmbeddedDocument within the User Document. Thus, you don't need a nested serializer for Contacts. Write it like this:

class User(Document):
    FirstName = StringField()
    LastName = StringField()
    Email = EmailField()
    Country = StringField()
    City = StringField()
    ProfilePic = URLField()
    Contacts = EmbeddedDocumentListField(ContactDetail, required=False)  # this is a list of embedded documents, e.g. [{PresentAddress: "", PermanentAddress: "", PhoneNumber: ""}, {PresentAddress: "", PermanentAddress: "", PhoneNumber: ""}], required=False allows it to be empty

class ContactDetail(EmbeddedDocument):  # note, this is EmbeddedDocument, not a separate Document
    PresentAddress = StringField()
    PermanentAddress = StringField()
    PhoneNumber = StringField()

class UserSerializer(DocumentSerializer):
    class Meta:
        model = User

class UserViewSet(ModelViewSet):
    lookup_field = 'id'
    serializer_class = UserSerializer

    def get_queryset(self):
        return User.objects.all()

Typically, you don't need to create nested serializers by hand because DRF-Mongoengine's DocumentSerializer automatically parses/writes subdocuments. In rarest cases, if you know what you're doing and really-really need to create a nested serializer, you'lll have to override create()/update() methods for the parent serializer as DRF-Mongoengine inherited the ban for writable nested serializers from DRF.

BurkovBA avatar Aug 23 '16 08:08 BurkovBA

This release of DRFM is based on DRF 3.3, which did not had writable nested. (I invented ComboReferenceField as workaround)

But it is good news they have such a thing. We can now move forward to 3.4

qwiglydee avatar Aug 23 '16 13:08 qwiglydee

@BurkovBA It's a simplified version of my requirement to get the point across. Using EmbeddedDocument will not be suitable as it is referenced by other models too.

@qwiglydee I did not find ComboReferenceField in Documentation and I cannot clearly understand how to use it. Is there any example or docs for this. I would also like to know when will the support for DRF 3.4 be available?

ShipraShalini avatar Aug 25 '16 06:08 ShipraShalini

@ShipraShalini it is experimental feature, not documented yet. I was going to put it in 'contrib' section.

qwiglydee avatar Aug 25 '16 07:08 qwiglydee

@qwiglydee I'd like to use ComboReferenceField in my project too, but I'm not sure about how to use it. Am I right that mongoengine Document field, corresponding to ComboReferenceField, either contains a reference to another Document (in that case corresponding Mongoengine Document field contains id of referenced Document) or contains the data itself, as if it were an EmbeddedDocument?

Is the following code an appropriate way to define ComboReferenceField? I mean, should I just use a DynamicField in Mongoengine Document to back DRFME's ComboReferenceField?

class MyDocument(Document):
    embed_or_reference = DynamicField()


class MyDocumentSerializer(DocumentSerializer):
    embed_or_reference = ComboReferenceField()

    class Meta:
        model = MyDocument

What's the current status of ComboReferenceField? Does it work?

And a related question. So far, DRFME github repostiory doesn't contain documentation at all, so we can not update http://umutbozkurt.github.io/django-rest-framework-mongoengine/ as we don't have the sources. It seems to use Sphinx, not mkdocs right? Are you planning to store it in the repository as DRF does?

And I wanted to say that I'm working on issues, I posted. Had to dig through call graph to undertsand what's going on with custom ids, working on that issue now.

BurkovBA avatar Aug 25 '16 09:08 BurkovBA

ComboReference can parse (to_internal_value): str or dict {_id} - to DBRef, like usial ReferenceField, other dict - to unsaved model instance. representation depends on depth of parent serializer - either objectid, or nested object The problem is that you need to extend serializer save/create/update method to handle the created model. This is not very good approach. Writable nested seems to be better.

qwiglydee avatar Aug 25 '16 12:08 qwiglydee

Documentation is in separate branch gh-pages - the branch that automatically deployed on github.io. To generate docs, there only needed is to checkout that branch and run make checkout && make pages

qwiglydee avatar Aug 25 '16 12:08 qwiglydee

@ShipraShalini Just wanted to mention, that use of NestedSerializers is a classical way to crush your performance in DRF: http://ses4j.github.io/2015/11/23/optimizing-slow-django-rest-framework-performance/

BurkovBA avatar Aug 26 '16 10:08 BurkovBA

Is there anyway to get the referenceField's data in validated_data so that I can override the create and update function to update/create the reference fields from the Parent DocumentSerializer? Also, I never get the instance in validated_data, when I explicitly include instance of the reference object in data to be validated and have to query again to assign it.

ShipraShalini avatar Sep 02 '16 05:09 ShipraShalini

I figured it out with ComboReferenceField, though it's not included in PyPI package.

ShipraShalini avatar Sep 02 '16 09:09 ShipraShalini

@ShipraShalini Great to hear that! PyPI package is currently broken, IIRC. I suggest that you use latest commits straight from github. In fact, we're preparing several pull requests with bugfixes in DictField(required=True) parsing, MapField(EmbeddedField), String field regex validator and other stuff.

BurkovBA avatar Sep 08 '16 13:09 BurkovBA