1

I'm not sure how to pass data from two 'nested' models to an endpoint.

I want to set up some data related to the user in my Django application. After some googling I found the best way to 'extend' the default user model is to create another model with a OneToOneField pointing to the user. This is how the model looks like:

# models.py

from django.db import models
from django.contrib.auth.models import User

class UserData(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    currency = models.CharField(max_length=10, default="USD")
    budget = models.IntegerField(default=500)
    showCategories = models.BooleanField(default=True)
    showLocations = models.BooleanField(default=True)
    showSources = models.BooleanField(default=True)

    def __str__(self):
        return self.user

After writing the model, I wrote a new serializer for it:

# serializers.py

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'email', 'username')

class UserDataSerializer(serializers.ModelSerializer):
    user = UserSerializer()

    class Meta:
        model = UserData
        fields = ('currency', 'budget', 'showCategories', 'showLocations', 'showSources', 'user')

All good so far (I hope).

Finally, I've changed my UserAPI to have a serializer_class of UserDataSerializer. This is how it looks right now:

# api.py

class UserAPI(generics.RetrieveAPIView):
    permission_classes = [
        permissions.IsAuthenticated,
    ]

    serializer_class = UserDataSerializer

    def get_object(self):
        # I suppose I need to return something else here but I'm not sure what.
        return self.request

When accessing my endpoint I get this data:

{
  "user": {
    "id": 1,
    "email":"[email protected]",
    "username":"myusername" 
  }
}

but I expected to get everything in UserData like this:


{
  "user": {
    "id": 1,
    "email": "[email protected]",
    "username": "myusername"
  },
  "currency": "USD",
  "budget": 500,
  "showCategories": true,
  "showLocations": true,
  "showSources": true
}

Edit:

This is how my urls.py looks like:

from django.urls import path, include
from .api import RegisterAPI, LoginAPI, UserAPI
from knox import views as knox_views
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path('api/auth/', include('knox.urls')),
    path('api/auth/user', UserAPI.as_view()),
    path('api/auth/register', RegisterAPI.as_view()),
    path('api/auth/login', LoginAPI.as_view()),
    path('api/auth/logout', knox_views.LogoutView.as_view(), name='knox_logout'),

    path('api/token/', TokenObtainPairView.as_view()),
    path('api/token/refresh', TokenRefreshView.as_view())
]
5
  • You can add a method to generate the dict on your own. Commented Jun 29, 2019 at 17:17
  • 1
    Are you sure that view is actually being called? I would expect an error from this code. Commented Jun 29, 2019 at 17:26
  • @DanielRoseman If by view you mean the code in api.py (sorry I'm pretty new to django), then yes I'm sure. Commented Jun 29, 2019 at 17:36
  • How are you sure? As I say, I would expect an error here because get_object returns the request, which is not serializable by UserDataSerializer. What you wouldn't get would be the data from a completely different serializer. Commented Jun 29, 2019 at 17:45
  • I used the good old print in UserAPI, that's how I thought the view was called, but now I'm not so sure anymore. Commented Jun 29, 2019 at 17:57

2 Answers 2

1

If you are using UserDataSerializer in api view, then get_object() should return the instance of model UserData, so that it's serializer knows which fields to serialize and how to get it's value.

Try this, i hope this will solve your issue.

class UserAPI(generics.RetrieveAPIView):
    permission_classes = [ permissions.IsAuthenticated,]
    serializer_class = UserDataSerializer
    queryset = UserData.objects.all()

    def get_object(self):
        return self.request.user.userdata

Recommendation:

You should create UserSerializer serializer and do reverse nested UserDatamodel serialization in UserSerializer, because User model will always have instance for each user, but it's extra info which you save in UserData model may or may not exist. SO, it's better to serialize primary resource. Then, your data will look like this, which of course you can change it's structure as you like.

{
  "id": 1,
  "email": "[email protected]",
  "username": "myusername",
  "userdata": {
    "currency": "USD",
    "budget": 500,
    "showCategories": true,
    "showLocations": true,
    "showSources": true
  }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This works great, thank you. Just one more thing to clarify, if I were to return self.request.user I would get the error 'User' object has no attribute 'user'. Do you know why this happends?
I already told you, as your using UserDataSerializer serializer, in which you are defined user in fields section, so while deserialize it try to get that attribute from the instance which you passed to it in constructor. So, to deserialize the instance successfully, you should pass UserData model instance to it, not User model, otherwise it give these no attribute error due to non existence of these attributes in given instance. I hope, now you understand it.
1

Not sure how your UserAPI view returned a result without a defined queryset

Try this

class UserAPI(generics.RetrieveAPIView):
    permission_classes = [ permissions.IsAuthenticated,]
    serializer_class = UserDataSerializer
    queryset = UserData.objects.all()
    lookup_field = 'pk'

Depending on your Django version your URL endpoint should include the lookup_field, which is usually pk by default for DRF.

url(r'^user/(?P<pk>\d+)/$', views.User.as_view())

So accessing ...../user/4/ returns a UserData instance with id of 4.

2 Comments

I've updated my question with my urls.py file, I'm not sure if that helps. I've edited my code as you said but it returns the same thing so I'm not sure that view is actually being called, as @DanielRoseman suggested.
@Cristi I see change use path('api/auth/user/(?P<pk>\d+)/$', UserAPI.as_view()) and maybe remove make the permission_class by making it empty like so permission_classes = [ ] and try visiting the url again

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.