umutbozkurt / django-rest-framework-mongoengine

Mongoengine support for Django Rest Framework
MIT License
614 stars 166 forks source link

Validation error with MapField(EmbeddedField()) on PUT request #224

Open Vayel opened 7 years ago

Vayel commented 7 years ago

Hi!

I have the following code:

from django_mongoengine import Document, EmbeddedDocument, fields
from rest_framework_mongoengine import serializers as mongoserializers
from rest_framework import serializers

class Embedded(EmbeddedDocument):
    date = fields.DateTimeField(required=True,)

class MyDoc(Document):
    children = fields.MapField(fields.EmbeddedDocumentField('Embedded'))

class EmbeddedSerializer(mongoserializers.EmbeddedDocumentSerializer):
    class Meta:
        model = Embedded
        fields = '__all__'

class MyDocSerializer(mongoserializers.DocumentSerializer):
    children = serializers.DictField(child=EmbeddedSerializer())

    class Meta:
        model = MyDoc
        fields = '__all__'

If I remove the redefinition of MyDocSerializer.children, it works well. But with the code above, I obtain the following error on a PUT (or PATCH) request (sent from the browsable api):

{
    "id": "593edc0ab15ab3250d1a0124",
    "children": {
        "key": {
            "date": "2016-05-05T08:00:00Z"
        }
    }
}
ValidationError (MyDoc:593edc0ab15ab3250d1a0124) (key.Invalid embedded document instance provided to an EmbeddedDocumentField: ['children'])

I have no errors with POST:

{
    "children": {
        "key": {
            "date": "2016-05-05T07:00:00Z"
        }
    }
}

Thanks

BurkovBA commented 7 years ago

@Vayel Hi, Vincent!

Do you realize, that normally you don't have to create serializers for EmbeddedDocuments and fields for serializers by hand?

So, the following code should be enough:

class Embedded(EmbeddedDocument):
    date = fields.DateTimeField(required=True,)

class MyDoc(Document):
    children = fields.MapField(fields.EmbeddedDocumentField('Embedded'))

class MyDocSerializer(mongoserializers.DocumentSerializer):
    class Meta:
        model = MyDoc
        fields = '__all__'

DocumentSerializer creates all the necessary fields and EmbeddedSerializers itself.

Vayel commented 7 years ago

@BurkovBA Yes, I do. It is a minimal example. What I really want to do is:

class EmbeddedSerializer(mongoserializers.EmbeddedDocumentSerializer):
    date = serializers.DateTimeField(format="%Y-%m-%d", input_formats=["%Y-%m-%d"])  # Bypass the absence of mongoengine.DateField

    class Meta:
        model = Embedded
        fields = '__all__'
BurkovBA commented 7 years ago

@Vayel Oh, but there is a DateTimeField in Mongoengine and it is automatically mapped to DRF's DateTimeField. Is it missing some functionality, you're looking for?

Vayel commented 7 years ago

@BurkovBA I would like to use the DateTimeField as a DateField (for a document publication date, I do not care about hours and minutes), that is to be able to make such a request:

{
    "children": {
        "key": {
            "date": "2016-05-05"
        }
    }
}
BurkovBA commented 7 years ago

@Vayel

Ah, I see. Well, I'll have to jump into debugger with this one to figure out, what went wrong. I'll write about my results, when I'm done.

BurkovBA commented 7 years ago

@Vayel

Took me a while to figure out: don't import DictFields from serializers - I had to override DictField in DRF-ME to support parsing mongoengine.MapField arguments into DictField options. Instead, use rest_framework_mongoengine.fields.DictField. Sorry about the confusion.

Vayel commented 7 years ago

@BurkovBA

It works, thanks a lot!

Vayel commented 7 years ago

Just in case someone would like to use DateTimeField as a DateField, I think the cleanest way is to use extra_kwargs:

class CollectedDocumentSerializer(mongoserializers.EmbeddedDocumentSerializer):
    class Meta:
        model = models.CollectedDocument
        fields = '__all__'

        # Should be in CollectionSerializer.Meta.extra_kwargs ("documents.child.xxx"),
        # but it does not work yet. See #215
        extra_kwargs = {
            'collected_on': {
                'format': '%Y-%m-%d',
                'input_formats': ['%Y-%m-%d'],
            },
            'description': {'allow_blank': True},
        }

class CollectionSerializer(mongoserializers.DocumentSerializer):
    documents = mongofields.DictField(child=CollectedDocumentSerializer(), required=False,)

    class Meta:
        model = models.Collection
        fields = '__all__'
        read_only_fields = ('created_on', 'updated_on',)
        extra_kwargs = {
            'created_on': {'format': '%Y-%m-%d'},
            'updated_on': {'format': '%Y-%m-%d'},
        }