Serialize data sent by EmberJS

84 views
Skip to first unread message

Kamilla Holanda

unread,
Aug 26, 2020, 9:32:04 AM8/26/20
to Django REST framework
Hey there! I am new to DRF and right now I'm working on a project with DRF + EmberJS. It is working perfectly to show data. 
The next step is to POST data and I am facing some issues that I couldn't solve by myself. Let me show you:

I have this model with lots of relationships:

---------------------------------------------------------------------------------------------------------------
class Propriedade(models.Model):
    nome = models.CharField(max_length=500)
    tipo = models.ForeignKey(
        TipoPropriedade, on_delete=models.CASCADE, null=True)
    estrelas = models.IntegerField()
    distancia_centro = models.FloatField()
    endereco = models.ForeignKey(
        'enderecos.Endereco', on_delete=models.CASCADE)
    proprietario = models.ForeignKey(
        'usuarios.Usuario', on_delete=models.CASCADE)
    estacionamentos = models.ManyToManyField(Estacionamento, blank=True)
    recepcao = models.ForeignKey(TipoRecepcao, on_delete=models.CASCADE)
    comodidades = models.ManyToManyField(ComodidadePropriedade, blank=True)
---------------------------------------------------------------------------------------------------------------

That's the serializer:

---------------------------------------------------------------------------------------------------------------
class PropriedadeSerializer(WritableNestedModelSerializer):
    tipo = serializers.PrimaryKeyRelatedField(queryset=TipoPropriedade.objects.all())

    endereco = serializers.PrimaryKeyRelatedField(queryset=Endereco.objects.all())

    proprietario = serializers.PrimaryKeyRelatedField(queryset=Usuario.objects.all())

    estacionamentos = serializers.PrimaryKeyRelatedField(many=True, queryset=Estacionamento.objects.all())

    recepcao = serializers.PrimaryKeyRelatedField(queryset=TipoRecepcao.objects.all())

    comodidades = serializers.PrimaryKeyRelatedField(many=True, queryset=ComodidadePropriedade.objects.all())

    class Meta:
        model = Propriedade
        fields = '__all__'
    class Meta:
        ordering = ['id']

    def __str__(self):
        return "%s" % (self.nome)
---------------------------------------------------------------------------------------------------------------

That's the ViewSet:

---------------------------------------------------------------------------------------------------------------
class PropriedadeViewSet(viewsets.ModelViewSet):
    queryset = Propriedade.objects.all()
    serializer_class = PropriedadeSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    resource_name = 'propriedades'

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        print(request.data)

        try:
            serializer.is_valid(raise_exception=True)
            try:
                nova_propriedade = serializer.save()
                print(nova_propriedade)
                headers = self.get_success_headers(serializer.data)
                return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
            except:
                print("Erro ao salvar")
        except:
            print(serializer.errors)

        return Response(status=status.HTTP_400_BAD_REQUEST)
---------------------------------------------------------------------------------------------------------------

And that's what I am getting from the client (EmberJS):

---------------------------------------------------------------------------------------------------------------
{'nome': 'Hotel AZ', 'estrelas': 3, 'distancia_centro': '12', 'proprietario': {'type': 'usuarios', 'id': '3'}, 'tipo': {'type': 'tipos-propriedade', 'id': '1'}, 'recepcao': {'type': 'tipos-recepcao', 'id': '3'}, 'comodidades': [{'type': 'tipos-comodidades-propriedade', 'id': '4'}, {'type': 'tipos-comodidades-propriedade', 'id': '2'}], 'endereco': {'type': 'enderecos', 'id': '32'}}
---------------------------------------------------------------------------------------------------------------

When I try to validate the serializer I get this error message:

---------------------------------------------------------------------------------------------------------------
{'tipo': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'endereco': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'proprietario': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'estacionamentos': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'recepcao': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')], 'comodidades': [ErrorDetail(string='Incorrect type. Expected pk value, received dict.', code='incorrect_type')]}
---------------------------------------------------------------------------------------------------------------

I get that it's expecting the pk instead of a dictionary with the values. But I am not sure how to get this working. I prefer to change things on the backend instead of making these changes on the frontend. 

I tried to change the fields to SlugRelatedField:

---------------------------------------------------------------------------------------------------------------
class PropriedadeSerializer(serializers.ModelSerializer):
    tipo = serializers.SlugRelatedField(queryset=TipoPropriedade.objects.all(), slug_field='id')

    endereco = serializers.SlugRelatedField(queryset=Endereco.objects.all(), slug_field='id')

    proprietario = serializers.SlugRelatedField(queryset=Usuario.objects.all(), slug_field='id')

    estacionamentos = serializers.SlugRelatedField(many=True, queryset=Estacionamento.objects.all(), slug_field='id')

    recepcao = serializers.SlugRelatedField(queryset=TipoRecepcao.objects.all(), slug_field='id')

    comodidades = serializers.SlugRelatedField(many=True, queryset=ComodidadePropriedade.objects.all(), slug_field='id')

    class Meta:
        model = Propriedade
        fields = '__all__'
---------------------------------------------------------------------------------------------------------------

But I am still getting these errors:

---------------------------------------------------------------------------------------------------------------
{'tipo': [ErrorDetail(string='Invalid value.', code='invalid')], 'endereco': [ErrorDetail(string='Invalid value.', code='invalid')], 'proprietario': [ErrorDetail(string='Invalid value.', code='invalid')], 'estacionamentos': [ErrorDetail(string='This field is required.', code='required')], 'recepcao': [ErrorDetail(string='Invalid value.', code='invalid')], 'comodidades': [ErrorDetail(string='Invalid value.', code='invalid')], 'estrelas': [ErrorDetail(string='This field is required.', code='required')]}
---------------------------------------------------------------------------------------------------------------


I also tried to use the drf-writable-nested:

---------------------------------------------------------------------------------------------------------------
class PropriedadeSerializer(WritableNestedModelSerializer):
    tipo = TipoPropriedadeSerializer()

    endereco = EnderecoSerializer()

    proprietario = UsuarioSerializer()

    estacionamentos = EstacionamentoSerializer(many=True)

    recepcao = TipoRecepcaoSerializer()

    comodidades = ComodidadePropriedadeSerializer(many=True)

    class Meta:
        model = Propriedade
        fields = '__all__'
---------------------------------------------------------------------------------------------------------------

But I got these errors:

---------------------------------------------------------------------------------------------------------------
{'tipo': {'tipo': [ErrorDetail(string='This field is required.', code='required')]}, 'endereco': {'logradouro': [ErrorDetail(string='This field is required.', code='required')], 'complemento': [ErrorDetail(string='This field is required.', code='required')], 'cep': [ErrorDetail(string='This field is required.', code='required')]}, 'proprietario': {'password': [ErrorDetail(string='This field is required.', code='required')], 'username': [ErrorDetail(string='This field is required.', code='required')], 'first_name': [ErrorDetail(string='This field is required.', code='required')], 'last_name': [ErrorDetail(string='This field is required.', code='required')], 'email': [ErrorDetail(string='This field is required.', code='required')], 'telefone_celular': [ErrorDetail(string='This field is required.', code='required')]}, 'estacionamentos': [{'tipo': [ErrorDetail(string='This field is required.', code='required')]}], 'recepcao': {'tipo': [ErrorDetail(string='This field is required.', code='required')]}, 'comodidades': [{'tipo': [ErrorDetail(string='This field is required.', code='required')]}, {'tipo': [ErrorDetail(string='This field is required.', code='required')]}, {'tipo': [ErrorDetail(string='This field is required.', code='required')]}]}
---------------------------------------------------------------------------------------------------------------

It seems like it's expecting all the embedded fields within the POST request but it's not the best approach to my project.

I also read the documentation about Wiritable Nested Serializers:

And I am confused. I will have to create all the relationships by myself? One by one? I am not sure how to deal with this to ManyToMany fields for example. 


So I am very confused about how to perform POSTs with DRF. What is the best approach to perform this action? Am I right to deal with this using PrimaryKeyRelatedField?
I also tried the rest_framework_json_api but it has lots of fields equired by the JSON:API and it makes everything more complex. 
I would appreciate your help and suggestion on how to solve this problem.

Thank you.

Alan Crosswell

unread,
Aug 26, 2020, 10:05:39 AM8/26/20
to django-res...@googlegroups.com
Hi Camilla,

I know almost nothing about EmberJS but I believe you may find that https://django-rest-framework-json-api.readthedocs.io/ may be helpful. It extends DRF to implement the JSONAPI.org spec, which I believe is originally based on Ember-data:

"ember-data is one of the original exemplar implementations. There is now an official adapter to support json-api."

--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-fram...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-rest-framework/37322b29-78d4-4e27-ad6a-89f0e7e267b9n%40googlegroups.com.

Kamilla Holanda

unread,
Aug 26, 2020, 2:44:56 PM8/26/20
to Django REST framework
Hey, Alan. Thank you for your answer. I have tried to define the fields as ResourceRelatedField but I think it's a lot to include and I don't need all these resources. I don't need necessarily to use JSON:API, I can set a RESTSerializer at EmberJS. I
 am still lost about posting data using DRF. Do I have to manipulate all the data manually and set the fields as the serializer expects? I thought the serializer would do the job.

Alan Crosswell

unread,
Aug 26, 2020, 2:58:36 PM8/26/20
to django-res...@googlegroups.com
If I am understanding your question correctly, then if when you are POSTing content the body has to contain JSON formatted basically the same way as a similar GET body and you need to see Content-Type: Application/json. I expect this is happening within EmberJS since it appears the JSONAPIAdapter is used by default.

JSONAPI doesn't require you to send/receive the related resources unless you explicitly ask for them via the include query param (for GET). You can also limit which fields are returned with sparse fieldsets.

Kamilla Holanda

unread,
Aug 31, 2020, 10:17:11 AM8/31/20
to Django REST framework
Thank you again, Alan. I've managed to get this working after reading carefully the DJA's documentation ( https://django-rest-framework-json-api.readthedocs.io/ ).

Here is what I have now:

------------------------------------------------------------------------------------------
from rest_framework_json_api import relations, serializers #it's very important to get this import

class PropriedadeSerializer(serializers.HyperlinkedModelSerializer):
    included_serializers = {
        'tipo': PropriedadeTipoSerializer,
        'endereco': EnderecoSerializer,
        'proprietario': UsuarioSerializer,
        'estacionamentos': EstacionamentoSerializer,
        'recepcao': RecepcaoTipoSerializer,
        'comodidades': PropriedadeComodidadeSerializer
    }

    tipo = relations.ResourceRelatedField(
        queryset=PropriedadeTipo.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    endereco = relations.ResourceRelatedField(
        queryset=Endereco.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    proprietario = relations.ResourceRelatedField(
        queryset=Usuario.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    estacionamentos = relations.ResourceRelatedField(
        queryset=Estacionamento.objects,
        many=True,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    recepcao = relations.ResourceRelatedField(
        queryset=RecepcaoTipo.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    comodidades = relations.ResourceRelatedField(
        queryset=PropriedadeComodidade.objects,
        many=True,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )


    class Meta:
        model = Propriedade
        fields = '__all__'

    class JSONAPIMeta:
        included_resources = ['tipo', 'endereco', 'proprietario',
                              'estacionamentos', 'recepcao', 'comodidades'
------------------------------------------------------------------------------------------------



-------------------------------------------------------------------------------------------------
class PropriedadeViewSet(viewsets.ModelViewSet):
    queryset = Propriedade.objects.all()
    serializer_class = PropriedadeSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    resource_name = 'propriedades'

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

    def perform_create(self, serializer):
        serializer.save()

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
---------------------------------------------------------------------------------------------------


--------------------------------------------------------------------------------------------------
from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter
from propriedades.views import *

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'propriedade-tipos', PropriedadeTipoViewSet,
                basename='propriedadetipo')
router.register(r'propriedades', PropriedadeViewSet, basename='propriedade')

# The API URLs are now determined automatically by the router.
urlpatterns = [
    url(r'', include(router.urls)),
    url(r'^propriedades/(?P<pk>[^/.]+)/$',
        PropriedadeViewSet.as_view({'get': 'retrieve'}),
        name='propriedade-detail'),
    url(r'^propriedades/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
        PropriedadeViewSet.as_view({'get': 'retrieve_related'}),
        name='propriedade-related'),
    url(regex=r'^propriedades/(?P<pk>[^/.]+)/relationships/(?P<related_field>[^/.]+)$',
        view=PropriedadeRelationshipView.as_view(),
        name='propriedade-relationships')
]
----------------------------------------------------------------------------

Now it works like a charm. Thank your for your help!

Alan Crosswell

unread,
Aug 31, 2020, 10:26:44 AM8/31/20
to django-res...@googlegroups.com
Glad to know that, Kamilla. I've found that JSONAPI's related vs. relationships paths are quite confusing at first. I'm glad you were able to get this to work with DJA.

I'm working on a DJA PR that will extend DRF 3.12 when it comes out that will help you document your JSONAPI-formatted API using Open API Specification 3 (OAS) with DRF's generateschema command.

Kamilla Holanda

unread,
Sep 3, 2020, 9:11:58 PM9/3/20
to Django REST framework
Thank you, Alan. That's a great work!

I keep working on my project and now I am struggling with some details about the related fields. When a try to access any related field I get this error:


---------------------------------------------------------------------------------
Request Method:
GETRequest URL:
http://localhost:8000/propriedades/1/tipo/Django Version:
3.0.8Exception Type:
AttributeErrorException Value:
'PropriedadeViewSet' object has no attribute 'retrieve_related'Exception Location:
/home/t319008/.virtualenvs/api-gestao-hotelaria/lib/python3.6/site-packages/rest_framework/viewsets.py in view, line 103Python Executable:
/home/t319008/.virtualenvs/api-gestao-hotelaria/bin/python3Python Version:
3.6.9

-------------------------------------------------------------------------------------

I am not sure about what I have to do to get these related fields working. My code right is like this:

------------------------------------------------------------------------------------
class PropriedadeSerializer(serializers.HyperlinkedModelSerializer):
    included_serializers = {
        'tipo': PropriedadeTipoSerializer,
        'endereco': EnderecoSerializer,
        'proprietario': UsuarioSerializer,
        'estacionamentos': EstacionamentoSerializer,
        'recepcao': RecepcaoTipoSerializer,
        'comodidades': PropriedadeComodidadeSerializer
    }

    tipo = relations.ResourceRelatedField(
        model=PropriedadeTipo,
        queryset=PropriedadeTipo.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    endereco = relations.ResourceRelatedField(
        model=Endereco,
        queryset=Endereco.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    proprietario = relations.ResourceRelatedField(
        model=Usuario,
        queryset=Usuario.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    estacionamentos = relations.ResourceRelatedField(
        model=Estacionamento,
        queryset=Estacionamento.objects,
        many=True,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    recepcao = relations.ResourceRelatedField(
        model=RecepcaoTipo,
        queryset=RecepcaoTipo.objects,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    comodidades = relations.ResourceRelatedField(
        model=PropriedadeComodidade,
        queryset=PropriedadeComodidade.objects,
        many=True,
        related_link_view_name='propriedade-related',
        self_link_view_name='propriedade-relationships'
    )

    class Meta:
        model = Propriedade
        fields = '__all__'

    class JSONAPIMeta:
        included_resources = ['tipo', 'endereco', 'proprietario',
                              'estacionamentos', 'recepcao', 'comodidades'
                              ]
------------------------------------------------------------------------------------

------------------------------------------------------------------------------------
class PropriedadeRelationshipView(RelationshipView):
    queryset = Propriedade.objects
    self_link_view_name = 'propriedade-relationships'
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

class PropriedadeViewSet(viewsets.ModelViewSet):
    queryset = Propriedade.objects.all()
    serializer_class = PropriedadeSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    resource_name = 'propriedades'


    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

    def perform_create(self, serializer):
        try:
            serializer.save()
        except ValueError:
            print(serializer.errors)

    def create(self, request, *args, **kwargs):
        print(request.data)
        serializer = self.get_serializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
        except ValueError:
            print(serializer.errors)

        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)

        if headers:
            return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

        return Response(serializer.data, status=status.HTTP_400_BAD_REQUEST, headers=headers)
-----------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------
from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter
from propriedades.views import *

# Create a router and register our viewsets with it.
router = DefaultRouter()
router.register(r'estacionamentos', EstacionamentoViewSet,
                basename='estacionamento')
router.register(r'estacionamento-tipos',
                EstacionamentoTipoViewSet, basename='estacionamentotipo')
router.register(r'recepcao-tipos', RecepcaoTipoViewSet,
                basename='recepcaotipo')
router.register(r'propriedade-comodidades',
                PropriedadeComodidadeViewSet, basename='propriedadecomodidade')
router.register(r'propriedade-tipos', PropriedadeTipoViewSet,
                basename='propriedadetipo')
router.register(r'propriedades', PropriedadeViewSet, basename='propriedade')

# The API URLs are now determined automatically by the router.
urlpatterns = [
    url(r'', include(router.urls)),
    url(r'^estacionamentos/(?P<pk>[^/.]+)/$',
        EstacionamentoViewSet.as_view({'get': 'retrieve'}),
        name='estacionamento-detail'),
    url(r'^estacionamentos/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
        EstacionamentoViewSet.as_view({'get': 'retrieve_related'}),
        name='estacionamento-related'),
    url(regex=r'^estacionamentos/(?P<pk>[^/.]+)/relationships/(?P<related_field>[^/.]+)$',
        view=EstacionamentoRelationshipView.as_view(),
        name='estacionamento-relationships'),
    url(r'^propriedades/(?P<pk>[^/.]+)/$',
        PropriedadeViewSet.as_view({'get': 'retrieve'}),
        name='propriedade-detail'),
    url(r'^propriedades/(?P<pk>[^/.]+)/(?P<related_field>\w+)/$',
        PropriedadeViewSet.as_view({'get': 'retrieve_related'}),
        name='propriedade-related'),
    url(regex=r'^propriedades/(?P<pk>[^/.]+)/relationships/(?P<related_field>[^/.]+)$',
        view=PropriedadeRelationshipView.as_view(),
        name='propriedade-relationships')
]
-------------------------------------------------------------------------------------

And I thought the retrieve_related was defined in my urls.py but I am still getting this error message. I've read the docs and searched the error but I couldn't figure out what's wrong with my related fields. Where should I define the retrieve_related attribute?

Thank you again for your help!

Kamilla Holanda

unread,
Sep 3, 2020, 9:37:54 PM9/3/20
to Django REST framework
Turns out that I just figured out what was happening: the problem was with my imports!

Before:
from rest_framework import permissions, status, viewsets.ModelViewSet

After:
from rest_framework_json_api.views import ModelViewSet, RelationshipView

I took 4 days to figure this out! But now it's solved and I'm glad! 

Thanks again!



Alan Crosswell

unread,
Sep 4, 2020, 8:55:38 AM9/4/20
to django-res...@googlegroups.com
Yes, DJA extends DRF classes, frequently with the same name. This makes it easy to convert from DRF to DJA by simply changing an import statement, but also hard to notice when you've not done so. I'm glad you were able to figure it out.

Reply all
Reply to author
Forward
0 new messages