I've 2 models:-
class Users(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255)
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users)
The user model & user address model. Following are the 2 serializers.
class UserAddressSerializer(ModelSerializer):
class Meta:
model = UserAddress
exclude = ('id', 'user')
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(many=True)
class Meta:
model = Users
fields = '__all__'
def create(self, validated_data):
address = validated_data.pop('address', [])
user = Users.objects.create(**validated_data)
for ad in address:
UserAddress.objects.create(user=user, **ad)
return user
The data I receive from the client is
{
"first_name": "string",
"last_name": "string",
"address": [{
"line1": "asd",
"country": "asd",
}],
}
This is how I create a new user and its corresponding address.
class UserCreate(GenericAPIView):
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserSerializer(data=data)
if not serializer.is_valid():
return
user = serializer.save()
response = {
'user_id': user.uuid
}
return
Now, upon getting the user details back, I receive an error saying
AttributeError: Got AttributeError when attempting to get a value for field `address` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Users` instance.
Original exception text was: 'Users' object has no attribute 'address'.
This is how I get the details of the user, including the address.
class UserDetails(GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
lookup_field = 'uuid'
def get(self, request, uuid, *args, **kwargs):
user = Users.get_user(uuid)
if not user:
return
serializer = UserSerializer(instance=user)
return
I'd read this example of nested relationship, and am doing exactly the same way. why is the error coming up?
Also, can this code be shorten up more (in a nicer clean way) using some DRF mixins? If yes, then how?
I think the most simple solution for your case is: in model UserAddress add related_name='address'
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users, related_name='address')
# ^^^^^^^^^^^^^^^
or you can add sourse property in serializer:
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(source='useraddress_set', many=True)
Serializer try to find attribute 'address' in the model User, but by default it is modelname underscore set (useraddress_set in your case), and you try other name, so you can set in the model or specify by source.
in the example you can look on models and find the related_name='tracks'
Related
I'm trying to create a nested serializer, UserLoginSerializer , composed of a UserSerializer and a NotificationSerializer, but I'm getting this error when it tries to serialize:
AttributeError: Got AttributeError when attempting to get a value for
field email on serializer UserSerializer. The serializer field
might be named incorrectly and not match any attribute or key on the
UserSerializer instance. Original exception text was:
'UserSerializer' object has no attribute 'email'.
Here is my models.py:
class Notification(models.Model):
kind = models.IntegerField(default=0)
message = models.CharField(max_length=256)
class User(AbstractUser):
username = models.CharField(max_length=150, blank=True, null=True)
email = models.EmailField(unique=True)
customer_id = models.UUIDField(default=uuid.uuid4, editable=False)
And my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"email",
"customer_id"
]
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
"id",
"kind",
"message",
]
class UserLoginSerializer(serializers.Serializer):
user_info = UserSerializer(read_only=True)
notifications = NotificationSerializer(many=True, read_only=True)
The error occurs at the last line in this endpoint:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user),
"notifications": NotificationSerializer(notifications, many=True),
}
)
return Response(serializer.data)
What am I doing wrong?
You can use .data attribute.
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user).data,
"notifications": NotificationSerializer(notifications, many=True).data,
}
)
return Response(serializer.data)
You must pass the data, not the serializer objects themselves. The data argument allows you to pass in a dictionary of data that will be used by the inner serializers to create a serialized representation. In this case, you will pass in the serialized data from the UserSerializer and NotificationSerializer to the UserLoginSerializer, which then returns the final serialized representation of the data.
Or, you may pass user and notifications directly as such:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": request.user,
"notifications": notifications
}
)
return Response(serializer.data)
Django Rest Framework model serializers have a to_representation method which converts the model instances to their dictionary representation, so in this case, the UserLoginSerializer will automatically use the UserSerializer and NotificationSerializer to serialize the user and notifications data. You can modify/change this method's behaviour by overriding it.
I hope this helps.
I am trying to write a nested serializer which would add serialize 2 models in the same view. Serialization seems to work fine since changes get reflected in the database but I am not able to get the many-to-many related field data in the response. I have been trying to figure out what the issue might be but still no progress.
Here is my code:
Model
class User(AbstractBaseUser):
AccountName = models.ManyToManyField(Account,
through='User_Account',
through_fields=('user', 'acc'),
related_name='AccountData',
blank=True)
EmailId = models.EmailField(max_length=128, blank=False, null=False)
USERNAME_FIELD = 'EmailId'
REQUIRED_FIELDS = ['AccountName']
class Account(models.Model):
AccountName = models.TextField(max_length=100, blank=False, null=False)
Serializer
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ["AccountName"]
class SignUpSerializer1(ModelSerializer):
AccountData = AccountCreationSerializer(read_only=True, many=True)
class Meta:
model = User
fields = ['EmailId', 'AccountData', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def validate(self, attrs):
attrs = super(SignUpSerializer1, self).validate(attrs=attrs)
attrs.update({"AccountData": self.initial_data.get("AccountData")})
return attrs
def create(self, validated_data):
AccountName_data = validated_data.pop('AccountData')
acc = Account.objects.create(AccountName=AccountName_data)
userAcc = User.objects.create_user(**validated_data)
if acc:
userAcc.AccountName.add(acc)
print("added")
return userAcc
View
class SignUpView(APIView):
serializer_class1 = SignUpSerializer1
def post(self, request, *args, **kwargs):
if request.data['CreateAccount']:
serializer = self.serializer_class1(data=request.data)
is_valid_serializer = serializer.is_valid(raise_exception=True)
if is_valid_serializer:
with transaction.atomic():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
raise Exception("Bad error")
Request1
"EmailId" : "xyz#gmail.com",
"AccountData":{"AccountName":"TestAcc1"},
"CreateAccount": true,
"password" : "xyz"
Response
"EmailId": "xyz#gmail.com",
#After Removing read_only=true from AccountData
Request2
"EmailId" : "xyz#gmail.com",
"AccountData":{"AccountName":"TestAcc1"},
"CreateAccount": true,
"password" : "xyz"
Response
{"AccountData":{"non_field_errors":["Expected a list of items but got type \"dict\"."]}}
Request3
"EmailId" : "xyz#gmail.com",
"AccountData":[{"AccountName":"TestAcc1"}],
"CreateAccount": true,
"password" : "xyz"
Response
Got AttributeError when attempting to get a value for field `AccountData` on serializer `SignUpSerializer1`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'AccountData'.
There is no response data fo AccountName in the response. And when I try print(User.objects.get(EmailId = serializer.data['AccountName'])) ====> None.
How should I get the field populated in the correct way in my response?
Thanks!
You need to specify source argument, since model's field called AccountName, not AccountData:
class SignUpSerializer1(ModelSerializer):
AccountData = AccountCreationSerializer(many=True, source="AccountName")
simple-jwt currently issues token using superuser but, i wanna use my custom User Model. (i defined custom User model as below.)
class User(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10, unique=True, blank=False)
password = models.CharField(max_length=128)
def __repr__(self):
return self.__class__
class UsersSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("name", "password")
my question is that could i receive token using custom user model at simple-jwt?
if simple-jwt uses custom User model, please tell me how to use custom User model.
This works for me try
Assuming you installed djangorestframework_simplejwt correctly
Just paste this at the end of your
settings.py
REST_AUTH_SERIALIZERS = {
'USER_DETAILS_SERIALIZER': 'users.serializers.UserSerializer',
}
here UserSerializer uses CustomUserModel i.e.
serializers.py
class UserSerializer(serializer.ModelSerializer):
class Meta:
model = get_user_model()
fields = []
read_only_fields = ( ,)
And add the serializer which overrides the default serializer validate method of the TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
# The default result (access/refresh tokens)
data = super().validate(attrs)
refresh = self.get_token(self.user)
# assign token
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
# extra fields
data['age'] = self.user.age
return data
With the help of this I get the "age" model field of the CustomUser i made,on calling the login end points.
User this serializer in your views class and then call the url of this views
After callilng this url
Returned data as follow:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..............",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...............",
"username": "Shubham",
"id": 4,
"age": 100,
"description": "Slow shubham gadwal mehra try to understand."
}
I am trying to implement multiple user types in DRF and I'm doing that by having a
User Model - which has login related fields and common fields in all the roles and also a choice field denoting type of user.
Customer Model - OneToOneField with user model.
Seller Model - OneToOneField with user model.
I have set up authentication and permissions for User model and now I'm able to log in. I want the logged in user to be able to create his respective profile (based on user_type field of User model).
class User(AbstractBaseUser, PermissionsMixin):
""" A Generic User inside our system. The fields used are common to all users in system. """
....
class Customer(models.Model):
"""A Class to represent a Customer in System """
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
Now I am trying to figure out how to allow a user to create a profile respective to his user_type (customer/seller). and the more confusing part is how do I set the user to current logged in user for my CustomerSerializer or SellerSerializer
This is the permission class I'm trying to use:
class UpdateCustomerProfile(permissions.BasePermission):
"""Allow users to edit their own profile """
def has_object_permission(self, request, view, obj):
"""Check if user is trying to edit their own profile"""
return obj.user.id == request.user.id
and this is the customer serializer:
class CustomerSerializer(serializers.ModelSerializer):
"""A Serizlier class for customer """
class Meta:
model = models.Customer
fields = ('user', 'first_name', 'last_name', 'dob', 'gender')
def create(self, validated_data):
"""Create and return a new customer."""
CustomerViewSet:
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
queryset = models.User.objects.filter( user_type = "CS" )
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.UpdateCustomerProfile,)
But I get an error
AttributeError at /api/customer-profile/
Got AttributeError when attempting to get a value for field user on serializer CustomerSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the User instance.
Original exception text was: 'User' object has no attribute 'user'.
I'm new to Django so I'm not sure If this is a way to do it or if I'm doing anything wrong. How can I fix this? Any examples projects following similar strategy would also be very helpful.
Since your serializer is for a Customer, your queryset should be for a Customer:
queryset = models.Customer.objects.filter(user=request.user)
for example, if you only want to the Customer profile of the current user.
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
#you are using customer model for serializer but for query set you are using
#User model.
queryset = models.Customer.objects.filter( user__type = "CS" )
authentication_classes = (TokenAuthentication,)
permission_classes = (permissions.UpdateCustomerProfile,)
Hey got my code below to work for serializing registration with multiple user types. I followed this: https://www.freecodecamp.org/news/nested-relationships-in-serializers-for-onetoone-fields-in-django-rest-framework-bdb4720d81e6/
here is my models.py:
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
is_individual = models.BooleanField(default=False)
is_company = models.BooleanField(default=False)
class Company(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
company_name = models.CharField(max_length=100)
email_address = models.EmailField(max_length=254, blank=True, null=True)
class Individual(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
email_address = models.EmailField(max_length=254)
here is my serializers.py:
from rest_framework import serializers
from classroom.models import User, Individual, Company
from django.contrib.auth import authenticate
class IndividualSerializer(serializers.ModelSerializer):
class Meta:
model = Individual
fields = ('user', 'email_address')
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = ('user', 'email_address', 'company_name')
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'password', 'is_individual', 'is_company')
extra_kwargs = {'password': {'write_only': True}}
class IndividualRegisterSerializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
class Meta:
model = Individual
fields = ('user', 'email_address')
extra_kwargs = {'password': {'write_only': True}, 'username': {'write_only': True}}
def create(self, validated_data, *args, **kwargs):
user = User.objects.create_user(validated_data['user']['username'], validated_data['email_address'], validated_data['user']['password'])
individual = Individual.objects.create(user=user, email_address=validated_data.pop('email_address'))
return individual
class CompanyRegisterSerializer(serializers.ModelSerializer):
user = UserSerializer(required=True)
class Meta:
model = Company
fields = ('user', 'company_name', 'email_address')
extra_kwargs = {'password': {'write_only': True}, 'username': {'write_only': True}}
def create(self, validated_data, *args, **kwargs):
user = User.objects.create_user(validated_data['user']['username'], validated_data['email_address'],
validated_data['user']['password'])
company = Company.objects.create(user=user, email_address=validated_data.pop('email_address'), company_name=validated_data.pop('company_name'))
return company
class IndividualLoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, data):
individual = authenticate(**data)
if individual and individual.is_active:
return individual
raise serializers.ValidationError("Incorrect Credentials")
class Meta:
fields = ['username','password','is_individual','is_company']
extra_kwargs = {'is_individual': {'required': False},
'is_company': {'required': False}}
and here is my api.py:
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import generics, permissions
from knox.models import AuthToken
from ..serializers import \
UserSerializer, \
IndividualRegisterSerializer, CompanyRegisterSerializer, \
class RegisterIndividualAPI(generics.GenericAPIView):
serializer_class = IndividualRegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
individual = serializer.save()
individual_data = IndividualSerializer(individual, context=self.get_serializer_context()).data
return Response({
"individual": individual_data,
"username": individual.user.username,
"token": AuthToken.objects.create(individual.user)[1]
})
class RegisterCompanyAPI(generics.GenericAPIView):
serializer_class = CompanyRegisterSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
company = serializer.save()
company_data = CompanySerializer(company, context=self.get_serializer_context()).data
return Response({
"company": company_data,
"username": company.user.username,
"token": AuthToken.objects.create(company.user)[1]
})
class LoginCompanyAPI(generics.GenericAPIView):
serializer_class = CompanyLoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
company = serializer.validated_data
company_data = CompanySerializer(company, context=self.get_serializer_context()).data
return Response({
"company": company_data,
"username": company.user.username,
"token": AuthToken.objects.create(company.user)[1]
})
And here is my API POST request's body:
{
"user": {
"username":"nind5",
"password": "123456",
"is_individual" : "True",
"is_company" : "False"
},
"email_address":"n#gmail.com"
}
I also used this resource https://www.youtube.com/watch?v=0d7cIfiydAc&t=437s. Knox is explained in this video as well.
Also, use this online tool to view your db (db.sqlite3) tables : https://sqliteonline.com/
Best,
Nick
When i try to add a Car for my user this is what's happening
"username": [
"A user with that username already exists."]
This is my models I'm using Django auth User model
There are my models
class Car(models.Model):
"""
CAR Table with a ForeingKey from USER Table
"""
user = models.ForeignKey(User, related_name='cars')
model = models.CharField(max_length=50)
color = models.CharField(max_length=20)
year = models.IntegerField()
seats = models.IntegerField()
def __unicode__(self):
return u'%d: %s: %s: %d' % (self.id, self.model, self.color, self.year)
My Serializers
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = Car
fields = (
'model',
'color',
'seats'
)
class AddCarSerializer(serializers.ModelSerializer):
car = CarSerializer()
class Meta:
model = User
fields = (
'username',
'car'
)
this is my View
class AddCarForUser(APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def put(self, request):
car = AddCarSerializer(data = request.data)
car.is_valid(raise_exception = True)
car.save()
return Response(status=status.HTTP_201_CREATED)
And this is what i'm sending in a Put Request
{
"username": "root",
"car": {
"model": "Seat Ibiza",
"color": "Verde",
"seats": "4"
}}
Your code doesn't works because you use serializer.ModelSerializer and the username attribute of theUser class must be unique, that why It can't validate it. To overcome this, follow these steps.
Update your serializer AddCarSerializer. We add a custom field username to handle username passed without a unique, just a simple CharField. And we add create function because Nested Serializer can't handle creation or update out the box:
class AddCarSerializer(serializers.Serializer):
# add username custom field
username = serializers.CharField()
car = CarSerializer()
class Meta():
fields = (
'username',
'car'
)
def create(self, validated_data):
""" Add car to an user
"""
tmp_car = validated_data.pop('car')
user = User.objects.get(username=validated_data['username'])
car = Car.objects.create(
user=user,
model=tmp_car['model'],
color=tmp_car['color'],
seats=tmp_car['seats'],
)
return car
update your view :
class AddCarForUser(APIView):
def put(self, request):
serializer = AddCarSerializer(data = request.data)
if serializer.is_valid():
serializer.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_400_BAD_REQUEST)
update your Car model, because year attribute doesn't exist when we add a car to an user then, add null=True :
class Car(models.Model):
user = models.ForeignKey(User, related_name='cars')
model = models.CharField(max_length=50)
color = models.CharField(max_length=20)
year = models.IntegerField(null=True)
seats = models.IntegerField()
And it should work. Don't forget to handle error like if the username passed doesn't exist.