Randall Hunt
Randall Hunt

Reputation: 12592

django rest framework lookup_field through OneToOneField

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.

Upvotes: 23

Views: 34007

Answers (5)

Barraguesh
Barraguesh

Reputation: 54

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)

Upvotes: 0

PrynsTag
PrynsTag

Reputation: 311

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'] 

Upvotes: 1

Sanjay Sikdar
Sanjay Sikdar

Reputation: 562

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>

Upvotes: 0

Todor
Todor

Reputation: 16050

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'

Upvotes: 13

mariodev
mariodev

Reputation: 15594

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',)

Upvotes: 0

Related Questions