Расширение модели пользователя

Я пытаюсь создать свой первый проект Django backend, поэтому я пытаюсь создать конечную точку REST API, которая получает регистрационные данные пользователя в json-файле от фронтенда и сохраняет их в базе данных, если они действительны. Я пытаюсь сохранить дополнительную информацию о пользователе в новой модели под названием Player и связать ее с моделью User по умолчанию, используя one-to-one-field.

Когда я получаю json файл с данными из front-end, в модели User создается новый пользователь с этими данными, также создается новая строка в модели Player, которая связана с пользователем, которого мы только что создали в модели User. Но проблема в том, что поля "рост" и "гандикап" остаются пустыми.

Я не знаю, как сохранить параметры "рост" и "гандикап" в новом экземпляре Player.

Это мой файл models.py:

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from datetime import *

# This model extend the basic built-in User model, by adding additional information on the uesr like
# handicap score anf height.
class Player(models.Model):

    def __str__(self):
        return self.user.username

    user = models.OneToOneField(User, on_delete=models.CASCADE)  # connecting this model to the User model
    # (cascade means when deleting a user row in the user table
    # the match row in this table will automatically will be deleted)
    handicap = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(28)])
    height = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(250)])
    registration_date = models.DateTimeField(default=datetime.now())

    #a listener that listen to the User model, if a new user as been save, it creates a new row in the player model with the new user in the user field
    @receiver(post_save, sender=User)
    def create_user_profile(sender, instance, created, **kwargs):
        if created:  # if a new user created in the User model
            Player.objects.create(user=instance)  # creating a new row in player, inserting the new user instance to the user field

    # if a User is saved we update the user instance to the player user field
    @receiver(post_save, sender=User)
    def save_user_profile(sender, instance, **kwargs):
        instance.profile.save()

Это мой файл serializers.py:

from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework.validators import UniqueValidator
from django.contrib.auth.password_validation import validate_password
from django.core.validators import MaxValueValidator, MinValueValidator
from .models import Player

# class serializer that handel the data from user registration
class RegisterSerializer(serializers.ModelSerializer):

    first_name = serializers.CharField(required=True)
    last_name = serializers.CharField(required=True)
    email = serializers.EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])  # making sure that the email that the user entered have not being used by another user
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])  # checking that the password is valid
    password2 = serializers.CharField(write_only=True, required=True)
    height = serializers.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(250)])
    handicap = serializers.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(28)])

    class Meta:  # nested class that gives the serializer details
        model = User
        fields = ('first_name', 'last_name', 'email', 'password', 'password2', 'height', 'handicap')

    # overriding the built-in validation method of the model serializer
    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:  # if the 2 passwords on the form don't match
            raise serializers.ValidationError({'password': "passwords don't match!"})  # raising an error
        return attrs

    # overriding the built-in create method
    def create(self, validated_data):
        # creating a user instance with the data came from the registration
        user = User.objects.create(username=validated_data['email'], first_name=validated_data['first_name'], last_name=validated_data['last_name'], email=validated_data['email'], password=validated_data['password'])
        user.save()  # saving the user registration data to the database
        player = Player.objects.get(user=user)
        player.height = validated_data['height']
        player.handicap = validated_data['handicap']
        player.save()
        return user

Вот мой файл views.py:

from rest_framework import generics
from .serializers import *

# Create your views here.
class RegistrationView(generics.CreateAPIView):
    queryset = User.objects.all()
    serializer_class = RegisterSerializer

Это мой файл urls.py:

from django.urls import path
from .views import RegistrationView

urlpatterns = [
    path('registration/', RegistrationView.as_view(), name='registration')
]

Кто-нибудь знает, что нужно сделать, чтобы также сохранить "рост" и "гандикап" в модели игрока?

Проблема, с которой вы столкнулись, может быть решена с помощью отношения сериализатора, а точнее с помощью вложенного отношения.

Я постарался сохранить ваш код настолько, насколько смог. Хотя некоторые изменения необходимы или просто были сделаны, чтобы сделать код чище.

models.py

from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save

class Player(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    handicap = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(28)])
    height = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(250)])

    @receiver(post_save, sender=User)
    def create_user_profile(sender, instance, created, **kwargs):
        if created:
            Player.objects.create(user=instance)

    def __str__(self):
        return self.user.username

Удалено поле 'registration_date'. Абстрактная модель пользователя уже имеет поле 'date_joined', нет необходимости хранить ту же информацию. Также, вторая функция 'save_user_profile ' была не нужна.

serializers.py

from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework.validators import UniqueValidator
from django.contrib.auth.password_validation import validate_password
from django.core.validators import MaxValueValidator, MinValueValidator
from core.models import Player

class PlayerSerializer(serializers.ModelSerializer):
    height = serializers.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(250)])
    handicap = serializers.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(28)])

    class Meta:
        model = Player
        exclude = ['user']

class RegisterSerializer(serializers.ModelSerializer):
    first_name = serializers.CharField(required=True)
    last_name = serializers.CharField(required=True)
    email = serializers.EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)
    player = PlayerSerializer()

    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email', 'password', 'password2', 'player')

    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({'password': "passwords don't match!"})
        return attrs

    def create(self, validated_data):
        pw2 = validated_data.pop('password2')
        player = validated_data.pop('player')

        user = User.objects.create(**validated_data)
        user.player.height = player['height']
        user.player.handicap = player['handicap']
        
        return user

Создали PlayerSerializer для вложения в RegistrationSerializer. Для представления полей 'handicap' и 'height' путем исключения поля 'user'.

В методе create извлекаем ключи из словаря, чтобы использовать **kwargs для более чистого формата. Позже, используя ключи для обновления значений отношений.

views.py и urls.py остаются нетронутыми.

Вы создали объект пользователя в сериализаторе. В следующей строке вы пытаетесь получить экземпляр пользователя из модели Player. На самом деле экземпляр пользователя еще не создан в модели Player. Поэтому сначала вы должны создать экземпляр пользователя в модели Player.

def create(self, validated_data):
    # first, get the data for player model from validated data
    height = validated_data.pop.get('height')
    handicap = validated_data.pop.get('handicap')

    # now create the user 
    user = User.objects.create(**validated_data)
    
    # now using the user instance, create the player object for that user.
    Player.objects.create(user=user, height=height, handicap=handicap)
    
    return user
Вернуться на верх