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",
...
}
}