I am trying to build my first Django backend project, so i'm trying to creatre a REST API end-point that gets a user registration data in json file from the front-end and save it on the database if it's valid.
I am trying to save the additional user information in a new model called Player and link it to the default User model using one-to-one-field.
When i recive the json file with the data from the front-end the a new user with the data is created in the User model, also a new row is created in the Player model that connected to the user we just created in the User model. But the problem is the fields "height" and "handicap" remain empty.
I don't know how to save the "height" and "handicap" parameters into the new Player instance.
This is my models.py file:
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()
This is my serializers.py file:
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
This is my views.py file:
from rest_framework import generics
from .serializers import *
# Create your views here.
class RegistrationView(generics.CreateAPIView):
queryset = User.objects.all()
serializer_class = RegisterSerializer
This is my urls.py file:
from django.urls import path
from .views import RegistrationView
urlpatterns = [
path('registration/', RegistrationView.as_view(), name='registration')
]
Does someone know what to do in order to also save the "height" and "handicap" to the Player model?
The problem you are facing can be solved with a serializer relation, more specifically with a nested relationship.
I have tried to preserve your code as much as I could. Although, some changes are necessary or just have been made to make the code cleaner.
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
Removed 'registration_date' field. The Abstract user model already has a field called 'date_joined', no need to store the same information. Also, the second function 'save_user_profile ' was not necessary.
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']
user.player.save()
return user
Created a PlayerSerializer to nest within the RegistrationSerializer. To represent the fields of 'handicap' and 'height' by excluding the 'user' field.
On the create method, poping keys out of the dictionary to use the **kwargs for a cleaner format. Later, using the keys to update relationship values.
views.py and urls.py remains untouched.
You created the user object in the serializer. In the next line you try to get the user instance from Player model. Actually that user's instance is not created in the Player model yet. So first you have to create that user instance in the Player model.
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
Related
I created a Django API to create a new user. However, when I try to create a user I get the error message:
IntegrityError at /api/v1/users/register/ NOT NULL constraint failed: accounts_user.user_id
This is what I have in models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
class User(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=100, blank=True)
location = models.CharField(max_length=100, blank=True)
password = models.CharField(max_length=32)
email = models.EmailField(max_length=150)
signup_confirmation = models.BooleanField(default=False)
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
if created:
User.objects.create(user=instance)
instance.profile.save()
In serializers.py
from rest_framework import serializers
from .models import User
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('user_id', 'name', 'location', 'password', 'email', 'signup_confirmation')
and my views.py
from rest_framework import viewsets
from .serializers import UserSerializer
from .models import User
from rest_framework.decorators import action
from .forms import SignUpForm
from .tokens import account_activation_token
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('name')
serializer_class = UserSerializer
#action (detail=True, methods=['post'])
def register(self, request):
print(request)
Any ideas on what I can do to resolve this error
As John wrote in a comment:
Here you have a problem: fields = ('user_id',...).
I also advise you to change your User model. If you don't need separating (I suppose you don't), it is way better to create your User with inheritance directly from AbstractUser instead of creating in fact doubled User models.
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
...
In this way you will already have username, password and email, but you can add anything else in same model, instead of using user.user etc.
Perhaps the question is wrongly worded. I created user profile using Django through the following blocks of code:
models.py
class = Profile (models.Models):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first name = models.CharField(max_length=50)
•••
serializer.py
class profile_serializer(serializers.ModerlSerializer)
class Meta:
model = Profile
fields = '__all__'
views.py
class profile_view(generics.ListCreateAPIView)
queryset = Profile.objects.all().filter(user=instance)
urls.py
urlspatterns = [path('profile', profile_view.as_view(), name='user_profile)
I definitely do not know how to implement the filter method to ensure that only the logged in user is retrieved. Or is there a better approach to obtain a specific user? If I use Project.objects.all() without the filter I get all the registered user as expected. But I don't know how to retrieve a particular user.
Hmm, I would do something like this:
from rest_framework.response import Response
from rest_framework import status, generics, permissions
class UserView(generics.GenericAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = profile_serializer
def get(self, request):
user = request.user
return Response(profile_serializer(user).data,status=status.HTTP_200_OK)
Basically when a user is authenticated, their user is present in the request.
Here's the UserSerializer, comments was hard to format. (Ironically for a tech forum?)
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
def get(self, instance):
return instance
def patch(self, instance, validated_data):
instance.model_method()
return super().update(instance, validated_data)
def create(self, validated_data):
user = self.context['request'].user
return super().update(user, validated_data)
And here's the profile_serializer:
class profile_serializer(serializers.ModerlSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Profile
fields = ('user', 'first_name', )
I'm starting to learn Django and have a class called Customer in my models.
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True)
cart = models.ManyToManyField(Product)
orders = models.ManyToManyField(Order)
def __init__(self, user):
self.user = user
I'm importing django.contrib.auth to register users to the database, but I would like to also initialize a Customer object upon registration.
I first attempted to override the save() method from the UserCreationForm and initialize a Customer object there:
class UserCreationForm(forms.ModelForm):
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
customer = Customer(user)
customer.save()
if commit:
user.save()
return user
But it did not seem to create a Customer object.
Alternatively, is it better to extend the User class to have the Customer class fields? I initially thought I should keep authentication separate, which is why I created the Customer class.
Might be better if you created a signal instead!
from django.db.models import signals
from django.dispatch import receiver
from django.contrib.auth.models import User
from path.to.models import Customer
#receiver(signals.post_save, sender = User)
def create_customer(sender, instance, created, *args, **kwargs):
if created:
c = Customer(...) #create your customer object
c.save()
and in apps.py, import signals to run it.
I'm new in Django rest framework, I tried my whole day but can't do it,I want to do full crud operation in my UserProfile Model which have a OneToOne field user, User can only update their own profile and in UserProfile create or update user shouldn't update User[username], How can i achieve it Please Help me
*** serializers.py ***
from rest_framework import serializers
from product.models import UserProfile
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
read_only_fields = ['username','password', ]
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False)
class Meta:
model = UserProfile
fields = "__all__"
def create(self, validated_data):
user_data = validated_data.pop('user')
user_instance = User.objects.get(
username=user_data['username'])
user_instance.save()
user_profile_instance = UserProfile.objects.create(
**validated_data, user=user_instance)
user_profile.save()
return user_profile
*** views.py ***
from django.shortcuts import render
from .serializers import UserProfileSerializer
from rest_framework.views import APIView
from rest_framework import generics, permissions
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth.models import User
from product.models import UserProfile
# Create your views here.
class CreateUserView(generics.ListCreateAPIView):
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticated,]
def get_queryset(self):
user = self.request.user
return UserProfile.objects.filter(user = user)
*** models.py ***
from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MaxValueValidator, MinValueValidator
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
country = models.CharField(max_length=50, default='India')
city = models.CharField(max_length=100, default='')
phone = models.CharField(max_length=15,default='')
image = models.ImageField(upload_to='profile_image', blank=True)
created_date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.user.username
For Authentication you can use token based authentication(like jwt)
and for username you can use read_only=True
no need to send the password for get request
to update profile you need to handle put/post methods
CLEANED Serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
read_only_fields = ['username','password']
class UserProfileSerializer(serializers.ModelSerializer):
# REMOVED all unecessary overrides
user = UserSerializer(read_only=True)
class Meta:
model = UserProfile
fields = "__all__"
Views:
class UserProfileViewSet(viewsets.GenericViewSet,
mixins.UpdateModelMixin):
# Changed inherited class and class NAME !
# I assume that your endpoint is something like /users/me/profile
# I think you want only to update user profile
# Listing or creating profile here is bad - user should have only ONE profile
# and you should do this on user model post_save signal
serializer_class = UserProfileSerializer
permission_classes = [permissions.IsAuthenticated,]
def get_object(self):
return self.request.user.user_profile
This setup will allow you to update profile and only profile data
In your models file you can make signal listener for automatically creating UserProfile object on User object create.
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
I'm trying to create a profile for my user using django rest auth model, but when I'm sending the data to the user's creation, the user's image is not being filled, that is, it's getting null, I tried to send the node as much as image, as much as profile.image, but without success below my code follows:
models\profile.py
from django.conf import settings
from django.db import models
class Profile(models.Model):
image = models.ImageField(blank=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.user.email
serializers\user.py
from rest_framework import serializers
from rest_framework.validators import UniqueValidator
from django.contrib.auth.models import User
from src.v1.user.models.profile import Profile
from .profile import ProfileSerializer
class UserSerializer(serializers.ModelSerializer):
profile = serializers.SerializerMethodField()
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
username = serializers.CharField(
max_length=32,
validators=[UniqueValidator(queryset=User.objects.all())]
)
password = serializers.CharField(min_length=6, write_only=True)
#staticmethod
def get_profile(user):
"""
Get or create profile
"""
profile, created = Profile.objects.get_or_create(user=user)
return ProfileSerializer(profile, read_only=True).data
def create(self, validated_data):
user = User(email=validated_data['email'],
username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'profile')
serializers\profile.py
from rest_framework import serializers
from src.v1.user.models.profile import Profile
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = '__all__'
class ProfileSerializerUpdate(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('image',)
views\user.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from src.v1.user.serializers.user import UserSerializer
from rest_framework.authtoken.models import Token
class UserView(APIView):
"""
Creates the user.
"""
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
token = Token.objects.create(user=user)
json = serializer.data
json['token'] = token.key
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
SerializerMethodField is read only by design. So it is not just the image, nothing is getting posted on the profile.
Also, creating the profile if it doesn't exist during a GET request is a bad design because generally, GET requests should be safe and not alter the state of the application.
You should instead, create the profile during user creation. Since there is just one field to be posted in the profile, it may be simpler and more effective to use a flat representation for the post data.
This is what you should do.
serializers/user.py
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(read_only=True)
image = serializers.ImageField(write_only=True)
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)
username = serializers.CharField(
max_length=32,
validators=[UniqueValidator(queryset=User.objects.all())]
)
password = serializers.CharField(min_length=6, write_only=True)
def create(self, validated_data):
user = User(email=validated_data['email'], username=validated_data['username'])
user.set_password(validated_data['password'])
user.save()
Profile.objects.create(user=user, image=validated_data['image')
return user
Now you can post your image field with the rest of the user fields