My database:
User
-----------------------------------
id | name
-----------------------------------
1 | Phuong Vu
-----------------------------------
Profile
-----------------------------------
user_id | phone_number
-----------------------------------
1 | 123456
-----------------------------------
My models:
class User(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True)
phone_number = models.CharField(max_length=15, unique=True, blank=True)
My serializers:
class ProfileSerializer(serializers.ModelSerializer):
def validate(self, attrs):
##################################
# I want get current instance here
##################################
print self.instance
return attrs
class Meta:
model = Profile
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(many=False)
class Meta:
model = User
This is in my view, I called is_valid 2 times in 2 ways to test:
profile_data = {
'phone_number':'123456'
}
user = User.objects.get(pk=1)
profile = Profile.objects.get(user_id=1)
setattr(user, 'profile', profile)
data = {
'profile': profile_data
}
serializer = UserSerializer(
user,
data=data,
partial=True
)
# This said 'Profile phone number is exist'
# because ProfileSerialiser.instance is None
serializer.is_valid()
# This is fine because ProfileSerialiser.instance is not None
profile_serialiser = ProfileSerialiser(profile, data=profile_data, partial=True)
profile_serializer.is_valid()
After create serializer instance, UserSerializer.instance is not None but children ProfileSerializer.instance is None.
So how can I fix this issue? (Access to self.instance in ProfileSerializer after it has created via UserSerialiser)
Related
I am trying to add some students to a teacher class using their ids as primary key but I am getting above error.
I have models of Teachers and Students like this.
class Student(TimeStampAbstractModel):
user = models.OneToOneField(User, related_name="student", on_delete=models.CASCADE)
college_name = models.CharField(max_length=255, default="", blank=True)
address = models.CharField(max_length=255, default="", blank=True)
def __str__(self):
return self.user.name
class Teacher(TimeStampAbstractModel):
user = models.OneToOneField(User, related_name="teacher", on_delete=models.CASCADE)
address = models.CharField(max_length=255, default="", blank=True)
students_in_class = models.ManyToManyField(Student,related_name="teacher")
def __str__(self):
return self.user.name
Here a teacher model can have many students in a class with thier ids. I have used an put api call to add the students to the teacher in one click.
My view:
from rest_framework import status
class AddtoClassView(APIView):
def put(self,request,pk,*args,**kwargs):
id =pk
teacher = Teacher.objects.get(id=id)
serializer = TeacherSerializer(teacher,data=request.data)
if serializer.is_valid():
serializer.save()
print("iam if")
return Response({
"message":"Student has been added to class.",
"data": serializer.data
},status=status.HTTP_200_OK)
# else:
print("iam else")
return Response(serializer.data)
My serializer:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
read_only= True
)
address = serializers.CharField(required=False)
# user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
# fields = '__all__'
def update(self, instance, validated_data):
instance.address = validated_data.get("address")
instance.save()
stu = validated_data.get("students_in_class")
print(stu)
if stu is not None:
print("iam stu")
instance.students_in_class.add(stu)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance
Here I have used students_in_class as pk field ( i still havent understand when to use integarfield and when to use pk field). I know since i am adding the ids to the student_in_class field i am not supposed to use it as read_only = true, however i had to use otherwise it generates error. How to solve this? Also, i dont really know which fields to define as which in serializer class.
Updated code:
class TeacherSerializer(serializers.ModelSerializer):
# students_in_class = serializers.PrimaryKeyRelatedField(
# many = True, read_only= True
# )
students_in_class = serializers.ListField(
source="students_in_class.all",
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
)
address = serializers.CharField(required=False)
# user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
# fields = '__all__'
def update(self, instance, validated_data):
instance.address = validated_data['students_in_class']['all']
instance.save()
stu = validated_data.get("students_in_class")
print(stu)
if stu is not None:
print("iam stu")
instance.students_in_class.add(stu)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance
Since you are using m2m field, you need list of ids for students_in_class. So the solution will be something like this. (Disclaimer: Code not tested).
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.ListField(
source="students_in_class.all",
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
)
Serialization error will be solved because now you have included students_in_class.all as source. You need to access the data with something like this: validated_data['students_in_class']['all']
If you want to serialize your output in different way, you could set students_in_class as write_only and override serializer representation as needed.:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
write_only=True
)
# your code
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['students_in_class'] = StudentSerializer(instance.students_in_class.all(), many=True).data
return ret
The following code worked:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many = True,queryset=Student.objects.all()
)
address = serializers.CharField(required=False)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
def update(self, instance, validated_data):
instance.address = validated_data.get("address")
instance.save()
stu = validated_data.pop("students_in_class")
for stus in stu:
instance.students_in_class.add(stus)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance
I'm working on extending the Basic User model to additional fields. I had created a Profile Model that should have a OneToOneRelation. I'm working with serializers. Now when I try to post a dummy user, I get this error:
**TypeError: User() got an unexpected keyword argument 'street'**
If I send only the user it works well. I know that the 'street' argument is not part of the User, but part of the Profile that should store later.
I tried to solve this with 'request.POST.pop' for every value and parsed to dict but then no data will be transmitted. Additionally, I had no success with Signals.
Does anyone have an idea how I can make this work, as the user and profile will be created at the same time? The user must save first and pass the id its generating to the Profile that is referenced to it.
Models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=CASCADE, null=True)
street = models.CharField(name="street", max_length=100)
number = models.CharField(name="number", max_length=10)
plz = models.CharField(name="plz", max_length=10)
city = models.CharField(name="city", max_length=100)
phone = models.CharField(name="phone", max_length=20)
locked = models.BooleanField(name="locked", default=False)
Serializer.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['street', 'number', 'plz', 'city', 'phone']
Views.py:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.create(validated_data=request.data)
profileSerializer = ProfileSerializer(instance=user ,data=request.data)
if profileSerializer.is_valid():
profileSerializer.create()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
You can rewrite the serializer to include profile. And then override the create method.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(**profile_data, user=user)
return user
Then your view becomes:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
The request to server should then be modified as:
{
profile: {
... // profile specific attributes like street, number, city
},
... // user specific attibutes
}
PS: To update the instance, you should override the update method of serializer.
I want to create a profile user when an instance of user is created, i get it but when i run the server it gives me TypeError saying that "NoneType" object is not iterable, i created a post_save signal in UserProfile model:
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
my UserProfile model is:
class UserProfile(TimeStampedModel):
MALE = 'M'
FEMALE = 'F'
NOT_SPECIFIED = 'NS'
GENDER_CHOICES = (
(MALE, _('Male')),
(FEMALE, _('Female')),
(NOT_SPECIFIED, _('Not specified'))
)
VALIDATOR = [validators.RegexValidator(re.compile('^[\w]+$'),
_('Only can has letters'), 'invalid')]
user_profile_id = models.AutoField(primary_key=True)
user = models.OneToOneField(auth_user, verbose_name=_('user'), blank=False, null=False,
on_delete=models.CASCADE, related_name='profile')
first_name = models.CharField(max_length=125, verbose_name=_('first name'),
validators=VALIDATOR, blank=True, null=False)
last_name = models.CharField(max_length=125, verbose_name=_('last name'),
validators=VALIDATOR, blank=True, null=False)
location = models.ForeignKey(Locations, on_delete=models.DO_NOTHING, verbose_name=_('location'),
related_name='location', blank=True, null=True)
profile_image = models.ImageField(verbose_name=_('profile image'), null=True)
gender = models.CharField(verbose_name=_('gender'), max_length=2, choices=GENDER_CHOICES, blank=True, null=True,
default=NOT_SPECIFIED)
DOB = models.DateField(verbose_name=_('date of birth'), blank=True, null=True)
occupation = models.TextField(verbose_name=_('occupation'), blank=True, null=False)
about = models.TextField(verbose_name=_('about'), blank=True, null=False)
I'm using django-rest-framework, django-rest-auth and django-allauth.
It's my UserProfileSerializer:
class UserProfileSerializer(UserDetailsSerializer):
# profile_image = ImageSerializer()
user = UserSerializer(source='profile', many=True)
class Meta:
model = UserProfile
fields = '__all__'
read_only_fields = ('created_at', 'updated_at',)
def update(self, instance, validated_data):
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.occupation = validated_data.get('occupation', instance.occupation)
instance.about = validated_data.get('about', instance.about)
instance.save()
return instance
def to_internal_value(self, data):
user_data = data['user']
return super().to_internal_value(user_data)
def to_representation(self, instance):
pass
When i access 127.0.0.1:800/rest-auth/user or 127.0.0.1:800/rest-auth/registration and register a user appear the following output:
TypeError at /rest-auth/user/
'NoneType' object is not iterable
Request Method: GET
Request URL: http://127.0.0.1:8000/rest-auth/user/
Django Version: 3.0.1
Exception Type: TypeError
Exception Value:
'NoneType' object is not iterable
Exception Location: C:\PROGRA~1\Python37\venv\lib\site-packages\rest_framework\utils\serializer_helpers.py in __init__, line 18
Python Executable: C:\PROGRA~1\Python37\venv\Scripts\python.exe
Python Version: 3.7.2
Python Path:
['C:\\Program Files\\Python37\\venv\\Scripts\\asta',
'C:\\PROGRA~1\\Python37\\venv\\Scripts\\python37.zip',
'C:\\PROGRA~1\\Python37\\venv\\DLLs',
'C:\\PROGRA~1\\Python37\\venv\\lib',
'C:\\PROGRA~1\\Python37\\venv\\Scripts',
'c:\\program files\\python37\\Lib',
'c:\\program files\\python37\\DLLs',
'C:\\PROGRA~1\\Python37\\venv',
'C:\\PROGRA~1\\Python37\\venv\\lib\\site-packages',
'C:\\Program Files\\Python37\\venv\\Scripts\\asta\\conf\\backend']
Server time: Qui, 6 Fev 2020 15:01:44 +000
My base.py(settings) is:
REST_FRAMEWORK = {
# CHANGE IT, to use oauth2 by django-oauth2-toolkit : already
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework_simplejwt.authentication.JWTAuthentication',
# 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
}
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'backend.users.serializers.SignUpSerializer'
}
REST_AUTH_SERIALIZERS = {
'USER_DETAILS_SERIALIZER': 'backend.users.serializers.UserProfileSerializer',
'LOGIN_SERIALIZER': 'backend.users.serializers.CustomLoginSerializer'
}
REST_USE_JWT = True
ACCOUNT_LOGOUT_ON_GET = True
OLD_PASSWORD_FIELD_ENABLED = True
SOCIALACCOUNT_QUERY_EMAIL = True
CORS_ORIGIN_ALLOW_ALL = True
JWT_AUTH = {
'JWT_VERIFY_EXPIRATION': False
}
When i change user = UserSerializer(source='profile', many=True) to user = UserSerializer(source='profile') I got other error :
AttributeError at /rest-auth/user/
Got AttributeError when attempting to get a value for field `username` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `UserProfile` instance.
Original exception text was: 'UserProfile' object has no attribute 'username'.
You should remove the many=True flag on the UserProfileSerializer
class UserProfileSerializer(UserDetailsSerializer):
# profile_image = ImageSerializer()
user = UserSerializer(source='profile')
According to the documentation:
If the field is used to represent a to-many relationship, you should add the many=True flag to the serializer field.
So a to-one relationship shouldn't have the many=True flag.
Edit
Ok so, after the edit another problem became clear. In your settings you change 'USER_DETAILS_SERIALIZER' in 'backend.users.serializers.UserProfileSerializer'. If you read the documentation of django-rest-auth about how the /rest-auth/user/ url is implemented you can see that they use UserDetailsView and pass an instance of User to the serializer. Thus either you make your custom UserDetailsView or, if you want to keep the implementation of the library, you need to extend the implementation of the library and handle the One-to-One Relationship there.
Note 1:
In Django Rest Framework nested serializers aren't writeable by default. So you'll need to implement the update logic yourself or you can seperate the UserProfile update logic from the UserDetailsView in your own view.
Note 2:
You can edit the RegisterSerializer of Django Rest Auth and make sure the UserProfile is created there so you can handle the 'nested creation' in that serializer instead of using a signal by getting the data in one API call.
Edit 2
As per request an example, quite opiniated trying to follow the package.
REST_AUTH_SERIALIZERS = {
# Changed
'USER_DETAILS_SERIALIZER': 'backend.users.serializers.CustomUserDetailsSerializer',
# Not changed
'LOGIN_SERIALIZER': 'backend.users.serializers.CustomLoginSerializer'
}
Make your CustomUserDetailsSerializer
# Get the UserModel
UserModel = get_user_model()
# Your UserProfileSerializer
class UserProfileSerializer(serializers.ModelSerializer):
# profile_image = ImageSerializer()
class Meta:
model = UserProfile
fields = '__all__'
read_only_fields = ('created_at', 'updated_at',)
# Custom serializer based on rest_auth.serializers.UserDetailsSerializer
class CustomUserDetailsSerializer(serializers.ModelSerializer):
"""
User model w/o password
"""
user_profile = UserProfileSerializer()
class Meta:
model = UserModel
fields = ('pk', 'username', 'email', 'user_profile')
read_only_fields = ('email', )
# Nested writes are not possible by default so you should implement it yourself
# https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
def update(self, instance, validated_data):
# Do updates on instance and save
# updates...
# eg. instance.username = validated_data.get('username', instance.username)
# get user profile data
user_profile_data = validated_data.pop('user_profile')
# access UserProfile through User instance one-to-one
user_profile = instance.profile
# do updates on user_profile with data in user_profile_data
# updates...
# save
user_profile.save()
return instance
Example JSON
{
"pk": 1,
"email": "test#test.com",
"username":"test",
"user_profile":{
"first_name":"Jane",
...
}
}
I have EndPoint that let user booking halls. But when i try to booking hall it give me error NOT NULL constraint failed. I lock at the problem 'NOT NULL constraint failed' after adding to models.py. they suggest to put null in the filed. But in my case i must save the hall id otherwise I can not know the hall that user is booking (it save null in the hall)
at the beginning it show this Error: NOT NULL constraint failed: api_bookingmodel.user_id and I solve it by putting in the function perform_create ... serializer.save(user=self.request.user)
This is my Code....
Model classes
class HallModel(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='hall_owner')
name = models.CharField(max_length=100)
price = models.FloatField()
phone = models.CharField(max_length=9, blank=True)
size = models.CharField(max_length=50)
region = models.CharField(max_length=100)
state = models.CharField(max_length=100)
image_1 = models.ImageField(upload_to='halls_image')
image_2 = models.ImageField(upload_to='halls_image')
image_3 = models.ImageField(upload_to='halls_image')
created_at = models.DateTimeField(auto_now_add=True)
class BookingModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_booking')
hall = models.ForeignKey(HallModel, on_delete=models.CASCADE, related_name='hall_owner')
time_date = models.DateTimeField(auto_now_add=True)
booking_method = models.IntegerField()
Serializer
class HallSerializer(serializers.ModelSerializer):
booking = serializers.SerializerMethodField()
class Meta:
model = HallModel
fields = '__all__'
def get_booking(self, obj):
booking = BookingSerializer(obj.hall_owner.all(),many=True).data
return booking
def perform_create(self, serializer):
serializer.save()
class BookingSerializer(serializers.ModelSerializer):
hall = serializers.SerializerMethodField()
class Meta:
model = BookingModel
fields = '__all__'
depth = 1
def get_hall(self, obj):
serializer_data = HallSerializer(obj.hall).data
return serializer_data
View Sets
class HallViewSet(viewsets.ModelViewSet):
queryset = HallModel.objects.all()
serializer_class = HallSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class BookingViewSet(viewsets.ModelViewSet):
serializer_class = BookingSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def get_queryset(self):
return self.request.user.user_booking.all()
it show this Error: IntegrityError at /api/booking/
NOT NULL constraint failed: api_bookingmodel.hall_id... I think i must save the hall id in the perform_create function but I don't able to do it. the stange things is when i delete the depth = 1 in the booking serializer it not show me the Error ... any one have a solution.
You can override def perform_create in BookingViewSet class, like below:
def perform_create(self, serializer):
serializer.save(
user=self.request.user,
hall=self.request.data['hall_id']
)
make sure that hall_id is ready in post request. Also def perform_create in HallSerializer is useless. read cdrf.co
I have following models:
class Person(User):
first_name = models.CharField(verbose_name=_('first name'), max_length=30)
last_name = models.CharField(verbose_name=_('last name'), max_length=30)
class Service(models.Model):
person = models.ForeignKey(Person, related_name='services')
price_from = models.DecimalField(_('price form'), max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])
price_to = models.DecimalField(_('price to'), max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.01'))])
class WorkPlace(models.Model):
service = models.OneToOneField('Service', related_name='work_place', primary_key=True)
city = CharField(verbose_name=_('city'), max_length=255)
street = CharField(verbose_name=_('street'), max_length=255)
I also registered Person in admin and made Service an inline admin.
Due to design, city and address are entered as multivalue field.
The problem is that I can't save WorkPlace manually.
Here are the forms:
class WorkPlaceForm(forms.ModelForm):
class Meta:
model = WorkPlace
fields = '__all__'
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = '__all__'
class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = super(ServiceForm, self).save(commit=commit)
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service # how???
work_place.save()
return service
Moreover, if I write service = super(ServiceForm, self).save(commit=True) on new object, this will raise Error as there is no Person created.
How can I solve this problem? Recall, that I'm working in admin.
class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = super(ServiceForm, self).save(commit=False)
service.member_id = service.member.pk # here is the key command
service.save()
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service # how???
work_place.save()
return service
I just needed to add member pk to service model
try this:
class ServiceForm(forms.ModelForm):
class Meta:
fields = '__all__'
model = Service
multi_work_place = MyMultiValueField()
def save(self, commit=True):
service = save_instance(self, self.instance, self._meta.fields,
fail_message, commit, self._meta.exclude,
construct=False)
service.save()
if self.cleaned_data['multi_work_place']:
work_place = WorkPlace(**{
'city': self.cleaned_data['multi_work_place'][0],
'street': self.cleaned_data['multi_work_place'][1],
})
# when there is brand new object is created, there is no service.pk
work_place.service = service
work_place.save()
return service