21

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.

Art
  • 2,836
  • 4
  • 17
  • 34
Randall Hunt
  • 12,132
  • 6
  • 32
  • 42
  • Have you tried using double underscores in the `lookup_field` to see if that can work? It'd be similar to a queryset filter. – Kevin Brown-Silva Mar 16 '15 at 03:35
  • 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'` – Randall Hunt Mar 16 '15 at 03:44
  • @ranman what do you want ? do you want to serialize a related object ? – levi Mar 16 '15 at 05:14
  • I want to be able to lookup the speaker object by the username on the user associated with the speaker – Randall Hunt Mar 16 '15 at 19:13

4 Answers4

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'
Art
  • 2,836
  • 4
  • 17
  • 34
Todor
  • 15,307
  • 5
  • 55
  • 62
  • 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 it – Randall Hunt Mar 18 '15 at 06:43
  • 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. – Todor Mar 18 '15 at 09:58
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'] 
PrynsTag
  • 191
  • 1
  • 10
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',)
mariodev
  • 13,928
  • 3
  • 49
  • 61
  • `'Speaker' object has no attribute 'user__username'` doesn't seem to work. I might have implemented it incorrectly. – Randall Hunt Mar 16 '15 at 19:16
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>

Sanjay Sikdar
  • 435
  • 4
  • 10