2013-08-29 8 views
33

Mam mój zapisany w dwóch różnych modelach, UserProfile i User. Teraz, z punktu widzenia interfejsu API, nikt nie dba o to, aby te dwa były inne.Django Rest Framework sprawiają, że statek relacji OnetoOne ma wrażenie, że jest to model

Więc tutaj mam:

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

i

class UserPSerializer(serializers.HyperlinkedModelSerializer): 
    full_name = Field(source='full_name') 
    class Meta: 
     model = UserProfile 
     fields = ('url', 'mobile', 'user','favourite_locations') 

Więc w UserPSerializer pole user jest tylko link do tego zasobu. Ale z perspektywy użytkownika naprawdę nie ma powodu, aby w ogóle wiedział o User.

Czy jest kilka sztuczek, dzięki którym mogę je po prostu zacierać i przedstawić je użytkownikowi jako modelowi lub muszę to zrobić ręcznie.

+0

Sprawdź [odpowiedzi na to pytanie] [1]. Myślę, że obejmuje to, czego potrzebujesz. [1]: http: // stackoverflow.com/questions/18012665 ​​/ how-can-one-customize-django-rest-framework-serializers-output/ –

+2

Ta odpowiedź jest również przydatna: http://stackoverflow.com/a/19806796/2789332 – Avril

+0

Pamiętaj, aby zaakceptować, jeśli jak odpowiedź poniżej! – bbengfort

Odpowiedz

12

Właśnie natknąłem się na to; Nie znalazłem jeszcze dobrego rozwiązania, szczególnie jeśli chodzi o pisanie do moich modeli User i UserProfile. Ja obecnie spłaszczenie mój serializers ręcznie przy użyciu SerializerMethodField, która jest niezwykle irytujące, ale to działa:

class UserSerializer(serializers.HyperlinkedModelSerializer): 

    mobile = serializers.SerializerMethodField('get_mobile') 
    favourite_locations = serializers.SerializerMethodField('get_favourite_locations') 

    class Meta: 
     model = User 
     fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations') 

    def get_mobile(self, obj): 
     return obj.get_profile().mobile 

    def get_favourite_locations(self, obj): 
     return obj.get_profile().favourite_locations 

Jest potwornie obsługi, ale trzeba skończyć z:

{ 
    "url": "http://example.com/api/users/1", 
    "username": "jondoe", 
    "first_name": "Jon", 
    "last_name": "Doe", 
    "email": "[email protected]", 
    "mobile": "701-680-3095", 
    "favourite_locations": [ 
     "Paris", 
     "London", 
     "Tokyo" 
    ] 
} 

Które, jak sądzę jest tym, czego szukasz.

+2

Myślę, że SerializerMethodField jest tylko do odczytu, więc nie pozwala na tworzenie lub modyfikowanie pól profilu za pośrednictwem zasobu użytkownika. Możesz również uzyskać tylko do odczytu * mniej * mniej pisania za pomocą sugestii w odpowiedzi @ kahlo. (Np. 'Mobile = serializers.CharField (source = 'profile.mobile', read_only = True)'). – medmunds

+0

'mobile = serializers.SerializerMethodField()' użyje domyślnie funkcji 'get_mobile' i wyświetli błąd dla nadmiarowości, jeśli ręcznie określisz pole. – andyn

6

chciałbym wdrożyć modyfikacje na UserPSerializer jako pola nie będzie rosnąć:

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

class UserPSerializer(serializers.HyperlinkedModelSerializer): 
    url = serializers.CharField(source='user.url') 
    username = serializers.CharField(source='user.username') 
    first_name = serializers.CharField(source='user.first_name') 
    last_name = serializers.CharField(source='user.last_name') 
    email = serializers.CharField(source='user.email') 

    class Meta: 
     model = UserProfile 
     fields = ('mobile', 'favourite_locations', 
        'url', 'username', 'first_name', 'last_name', 'email') 
+4

jest to czyste rozwiązanie do pobierania użytkowników, ale nie działa dla POSTing nowego użytkownika. – Xuan

+1

Po prostu zastąp aktualizację i utwórz .. –

29

Możesz pisać i PUT z @kahlo's approach jeśli również przesłonić metody tworzenia i aktualizacji na swojej serializatora.

Biorąc pod uwagę model profil tak:

class Profile(models.Model): 
    user = models.OneToOneField(User) 
    avatar_url = models.URLField(default='', blank=True) # e.g. 

Oto serializer użytkownika, zarówno odczytuje i zapisuje pole dodatkowy profil (s):

class UserSerializer(serializers.HyperlinkedModelSerializer): 
    # A field from the user's profile: 
    avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True) 

    class Meta: 
     model = User 
     fields = ('url', 'username', 'avatar_url') 

    def create(self, validated_data): 
     profile_data = validated_data.pop('profile', None) 
     user = super(UserSerializer, self).create(validated_data) 
     self.update_or_create_profile(user, profile_data) 
     return user 

    def update(self, instance, validated_data): 
     profile_data = validated_data.pop('profile', None) 
     self.update_or_create_profile(instance, profile_data) 
     return super(UserSerializer, self).update(instance, validated_data) 

    def update_or_create_profile(self, user, profile_data): 
     # This always creates a Profile if the User is missing one; 
     # change the logic here if that's not right for your app 
     Profile.objects.update_or_create(user=user, defaults=profile_data) 

Powstały API prezentuje płaską zasób użytkownika , zgodnie z życzeniem:

GET /users/5/ 
{ 
    "url": "http://localhost:9090/users/5/", 
    "username": "test", 
    "avatar_url": "http://example.com/avatar.jpg" 
} 

i można dołączyć profil avatar_url pole w żądaniach POST i PUT. (I usuwać na zasobie użytkownika będzie również usunąć swój model Profile, choć to po prostu normalne usuwania kaskadowego Django).

Logika tutaj będzie zawsze stworzenie modelu profil użytkownika, jeśli brakuje (po każdej aktualizacji). Z użytkownikami i profilami prawdopodobnie to właśnie chcesz. W przypadku innych relacji może nie być, i będziesz musiał zmienić logikę aktualizacji lub tworzenia. (Dlatego dla ciebie jest to DRF doesn't automatically write through a nested relationship).