django rest framework lookup_field through OneToOneField
Asked Answered
G

5

23

https://gist.github.com/ranman/3d97ea9054c984bca75e

Desired Behavior
User lookup happens by the username: /api/users/randall
Speaker lookup happens by the username as well: /api/speakers/randall

Constraints
Not all users are speakers. All speakers are users.

models.py

from django.contrib.auth.models import User

class Speaker(models.Model):
    user = models.OneToOneField(User)

serializers.py

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'groups')
        lookup_field = 'username'
 
class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        read_only=True,
        lookup_field='username'
    )
    class Meta:
        model = Speaker
        lookup_field = 'user'

views.py

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'
    
class SpeakerViewSet(viewsets.ModelViewSet):
    queryset = Speaker.objects.all().select_related('user')
    serializer_class = SpeakerSerializer
    lookup_field = "user"

I've tried various different invocations of lookup_field and serializer types to get this working to no avail. It may not be possible without a lot more code. I'm just wondering what direction I can take.

Gorki answered 16/3, 2015 at 0:54 Comment(4)
Have you tried using double underscores in the lookup_field to see if that can work? It'd be similar to a queryset filter.Keener
I've tried using lookup_field = "user__username" and it doesn't work. I've tried using that on both the model and the view to no avail :( 'Speaker' object has no attribute 'user__username'Gorki
@ranman what do you want ? do you want to serialize a related object ?Anstus
I want to be able to lookup the speaker object by the username on the user associated with the speakerGorki
D
13

This is how I managed to hack it

models.py

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

class Speaker(models.Model):
    user = models.OneToOneField(User)

    @property
    def user__username(self):
        return self.user.username
    
    def __unicode__(self):
        return self.user.username

serializers.py

from .models import Speaker
from rest_framework import serializers
from django.contrib.auth.models import User

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'email', 'groups')
        lookup_field = 'username'

class SpeakerSerializer(serializers.HyperlinkedModelSerializer):
    user = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        read_only=True,
        lookup_field='username'
    )
    class Meta:
        model = Speaker
        fields = ('url', 'user')
        lookup_field = 'user__username'

view.py

from .models import Speaker
from .serializers import SpeakerSerializer, UserSerializer

from rest_framework import viewsets
from django.contrib.auth.models import User

class UserViewSet(viewsets.ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    lookup_field = 'username'

class SpeakerViewSet(viewsets.ModelViewSet):
    queryset = Speaker.objects.all().select_related('user')
    serializer_class = SpeakerSerializer
    lookup_field = 'user__username'
Digitalize answered 16/3, 2015 at 9:42 Comment(2)
Actually it doesn't work -- for some reason this doesn't allow 2 things: the user field is no longer able to be written to when you create a new speaker, the speaker is no longer renderable on items that have relations to itGorki
How can i reproduce your errors? I mean I tried to create a new speaker via the python shell and it pass with no problems, if you try to create a speaker via the api, well the user field is marked as read-only, that's why it doesn't work. I also created a dummy model having OneToOneField to the speaker, this also worked with both ModelSerializer and HyperlinkedModelSerializer with the 2nd one i just had to redefine the speaker field with as HyperlinkedRelatedField with lookup_field='user__username'(the same thing was done in the SpeakerSerializer for the user field.Digitalize
S
1

The only thing I changed from your code is to override the get_object method by filtering with the username instead of the default pk. I also changed the lookup_field to a descriptive name and used ModelSerializer and StringRelated in the serializer.py.

models.py

class Speaker(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)

serializer.py

class SpeakerSerializer(serializers.ModelSerializer):
    user = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Speaker
        lookup_field = "username"
        fields = "__all__"

views.py

class SpeakerViewSet(ModelViewSet):
    queryset = Speaker.objects.all().select_related("user")
    serializer_class = SpeakerSerializer
    lookup_field = "username"

    def get_object(self):
        """Return the object for this view."""
        return get_object_or_404(self.queryset, user__username=self.kwargs["username"])

urlconf

 api/ ^speaker/$ [name='speaker-list']
 api/ ^speaker\.(?P<format>[a-z0-9]+)/?$ [name='speaker-list']
 api/ ^speaker/(?P<username>[^/.]+)/$ [name='speaker-detail']
 api/ ^speaker/(?P<username>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='speaker-detail'] 
Selfseeking answered 3/2, 2022 at 2:48 Comment(0)
D
0

Have you tried this approach?

class SpeakerViewSet(viewsets.ModelViewSet):
    queryset = Speaker.objects.all().select_related('user')
    serializer_class = SpeakerSerializer
    filter_backends = (filters.DjangoFilterBackend,)
    filter_fields = ('user', 'user__username',)
Derk answered 16/3, 2015 at 8:29 Comment(1)
'Speaker' object has no attribute 'user__username' doesn't seem to work. I might have implemented it incorrectly.Gorki
B
0

I'm fetching user's settings with user's ID [GET / Update]


urls.py

path('user/<int:user_id>/settings/preferences/', UserPreferenceSettingsView.as_view(), name="settings_preferences")

models.py

class UserSetting(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    adult_lock = models.BooleanField(default=False)
    child_lock = models.BooleanField(default=False)
    promotional_email = models.BooleanField(default=True)
    update_email = models.BooleanField(default=True)
    updated_at = models.DateTimeField(auto_now=True)

views.py

class UserPreferenceSettingsView(generics.RetrieveUpdateAPIView):
    http_method_names = ['get', 'patch']    
    serializer_class = UserPreferenceSettingsSerializer

    def get_object(self):
        lookup_field = self.kwargs["user_id"]
        return get_object_or_404(UserSetting, user__pk=lookup_field)

If you need to fetch from username just replace user_id to username and url <int:user_id> to <username> or <str:username>

Bramlett answered 19/11, 2021 at 9:31 Comment(1)
It might be useful to use return get_object_or_404(UserSetting, user__pk=self.kwargs.get(self.lookup_field))Xanthate
S
0

I stumbled across this question and none of the answers provided managed to solve my issue, the API request was still showing the 'pk' in the user field instead of the username.

I managed to get it working just adding a field to serializer.py

class PublicationSerializer(serializers.ModelSerializer):
    #Field I had to add
    author = serializers.SlugRelatedField(
        many=False,
        read_only=True,
        slug_field='username'
    )

    class Meta:
        model = Publication
        fields = '__all__'

My viewsets.py which didn't require anything new:

class PublicationViewSet(viewsets.ModelViewSet):
    queryset = Publication.objects.all()
    serializer_class = PublicationSerializer
    permission_classes = [IsAuthenticated]

And my models.py:

class Publication(models.Model):
    title= models.CharField(max_length=100)
    message = models.CharField(max_length=1000)
    author = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
Satterlee answered 25/4 at 19:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.