I would like to extend Django's user model using a OneToOneField in my Django api project, but I'm getting a weird error. I'm hoping someone can help me out. Below is my code and the error message.
models.py:
class Profile(models.Model):
yearOfExperience = models.PositiveIntegerField(default=1)
profession = models.CharField(max_length=250, blank=True, null=True)
dp = models.URLField(blank=True, null=True)
qualification = models.CharField(max_length=255, blank=True, null=True)
phoneNumber = models.CharField(max_length=255, blank=True, null=True)
#receiver(post_save, sender=CustomUser)
def create_user_profile(sender, instance=None, created=False, **kwargs):
if created:
Profile.objects.create(user=instance)
instance.profile.save()
serializer:
class ProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source = 'pk', read_only = True)
username = serializers.CharField(source = 'user.username', read_only = True)
email = serializers.CharField(source = 'user.email', read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def create(self, validated_data, instance=None):
if 'user' in validated_data:
user_data = validated_data.pop('user')
user = CustomUser.objects.create(**validated_data)
profile = Profile.objects.update_or_create(user=user, **validated_data)
return user
apiView:
class ProfileListView(generics.ListCreateAPIView):
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Error message:
File "/home/olaneat/Desktop/filez/project/django/funzone/lib/python3.7/site-packages/django/db/models/base.py", line 500, in __init__
raise TypeError("%s() got an unexpected keyword argument '%s'" % (cls.__name__, kwarg))
TypeError: CustomUser() got an unexpected keyword argument 'yearOfExperience'
I believe you're missing the user one to one field to your profile model so
add it there
class Profile(models.Model):
user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
...
Since you're using post_save for your profile, there's no need for the .create in your serializer. So something like this:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only = True)
email = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def get_username(self, obj):
return obj.user.username
def get_email(self, obj):
return obj.user.email
now just post from the API view and the profile will be created.
If however, you want to keep the .create for the option to add new users from the profile API then perhaps something like this:
class ProfileSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField(read_only = True)
email = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = ('id', 'email', 'username', 'yearOfExperience',
'qualification', 'profession', 'phoneNumber'
)
def create(self, validated_data, instance=None):
if 'user' in validated_data:
user = validated_data.pop('user')
else:
user = CustomUser.objects.create(**validated_data)
profile, created_profile = Profile.objects.update_or_create(user=user, **validated_data)
return profile
def get_username(self, obj):
return obj.user.username
def get_email(self, obj):
return obj.user.email
Related
I have these two models: order and route. The route has a oneToMany relation with Order as you can see:
class Order(models.Model):
customer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='customer')
retailer = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='retailer')
date_publish = models.DateField(default=datetime.date.today)
date_available = models.DateField()
weight = models.DecimalField(decimal_places=2, max_digits=5)
description = models.CharField(max_length=500, null=True)
route = models.ForeignKey(Route, related_name='orders', null=True, on_delete=models.CASCADE)
class Route(models.Model):
day = models.DateField()
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
start_time = models.TimeField()
When a route is created I want to associate orders with that route, so I've done the following serializer:
class routeSerializer(serializers.ModelSerializer):
orders = OrderSerializer(many=True)
class Meta:
model = Route
fields = ['day', 'warehouse', 'start_time', 'orders']
def create(self, validated_data):
orders_data = validated_data.pop('orders')
route = Route.objects.create(**validated_data)
for order_data in orders_data:
order_serializer = OrderSerializer(data=order_data)
order_serializer.is_valid(raise_exception=True)
orders = order_serializer.save()
orders.route = route
orders.save()
return route
class OrderSerializer(serializers.ModelSerializer):
ordertimelocation = orderTimelocationSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'customer', 'retailer', 'date_available', 'weight', 'description', 'ordertimelocation']
def create(self, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
order = Order.objects.create(**validated_data)
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = order
order_time_location.save()
return order
def update(self, instance, validated_data):
timelocations_data = validated_data.pop('ordertimelocation')
ordertimelocation = instance.ordertimelocation
for timelocation_data in timelocations_data:
order_time_location_serializer = orderTimelocationSerializer(data=timelocation_data)
order_time_location_serializer.is_valid(raise_exception=True)
order_time_location = order_time_location_serializer.save()
order_time_location.order = instance
order_time_location.save()
return instance
Views:
class GetRoutes(generics.ListAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateRoute(generics.CreateAPIView):
queryset = Route.objects.all()
serializer_class = routeSerializer
class CreateOrder(generics.CreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
class GetOrders(generics.ListAPIView):
serializer_class = OrderSerializer
def get_queryset(self):
us = self.kwargs.get('us')
return Order.objects.filter(customer_id=us)
class GetOrder(generics.RetrieveAPIView):
serializer_class = OrderSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('order')
return get_object_or_404(Order, id=item)
class UpdateOrder(generics.UpdateAPIView):
serializer_class = OrderSerializer
queryset = Order.objects.all()
Edit:
I also customised the default user model like this:
User models:
class UserManager(BaseUserManager):
def create_superuser(self, email, user_name, first_name, password, **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
if other_fields.get('is_staff') is not True:
raise ValueError(
'Superuser must be assigned to is_staff=True.')
if other_fields.get('is_superuser') is not True:
raise ValueError(
'Superuser must be assigned to is_superuser=True.')
return self.create_user(email, user_name, first_name, password, **other_fields)
def create_user(self, email, user_name, first_name, password, **other_fields):
if not email:
raise ValueError(_('You must provide an email address'))
email = self.normalize_email(email)
user = self.model(email=email, user_name=user_name, first_name=first_name, **other_fields)
user.set_password(password)
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
GENDER_MALE = 0
GENDER_FEMALE = 1
GENDER_OTHER = 2
GENDER_CHOICES = [(GENDER_MALE, 'Male'), (GENDER_FEMALE, 'Female'), (GENDER_OTHER, 'Other')]
email = models.EmailField(_('email address'), unique=True)
user_name = models.CharField(max_length=150, unique=True)
first_name = models.CharField(max_length=150, blank=True)
last_name = models.CharField(max_length=150, blank=True)
start_date = models.DateTimeField(default=timezone.now)
is_staff = models.BooleanField(default=False)
is_retailer = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
gender = models.IntegerField(choices=GENDER_CHOICES)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['user_name', 'first_name']
def __str__(self):
return self.user_name
def isretailer(self):
return self.is_retailer
User serizalizer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'user_name', 'first_name', 'last_name')
Views:
class CustomUserCreate(APIView):
permission_classes = [AllowAny] #when a user create an account he isn't autenticated
def post(self, request, format='json'):
serializer = RegisterUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
json = serializer.data
return Response(json, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class ListUsers(generics.ListAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
class UserDetail(generics.RetrieveAPIView):
serializer_class = UserSerializer
def get_object(self, queryset=None, **kwargs):
item = self.kwargs.get('id')
return get_object_or_404(User, id=item)
I am sending a request like this:
{
"day" : "2021-12-12",
"warehouse": "1",
"start_time": "7:00",
"orders": [
{
"id": 15,
"customer": 1,
"retailer": 2,
"date_available": "2020-12-12",
"weight": "1.20",
"description": null,
"ordertimelocation": [
{
"longitude": "12.1223000000000000",
"latitude": "12.1223000000000000",
"time_interval": [
{
"start": "2021-07-21T10:10:00Z",
"end": "2021-07-21T10:10:00Z"
}
]
}
]
}
]
}
But the server returns a bad request:
{
"customer": [
"Incorrect type. Expected pk value, received User."
],
"retailer": [
"Incorrect type. Expected pk value, received User."
]
}
I'm new to django and I don't know what is a 'pk' value and why it is expecting it instead of User.
PK is primary key and in here is equal to id
For saving a record in db with it's relations, django needs PK of related object
But when you pass this pk to serializer, in validate() function of serializer, django if passed pk is existed in db and valid, if so, it would return it's model object
for example, you pass customer pk as 1, but after validation, there is a Customer object with id 1. and you are passing this object to order = Order.objects.create(**validated_data) but as i mentioned before, you should pass just PK
So one of solutions can be:
validated_data['customer'] = validated_data['customer'].id
validated_data['retailer'] = validated_data['retailer'].id
order = Order.objects.create(**validated_data)
And another solution is to overriding validate() function and control what to return
I am working on a project and the models seemed to work fine until I added a feedback field in models. The data in the UserProfile table cannot be accessed and returns the above error.
models.py
Roles = (
('sales', 'SALES'),
('operations', 'OPERATIONS'),
('cashier', 'CASHIER'),
('frontdesk', 'FRONTDESK'),
('client', 'CLIENT'),
)
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE, default=None, null=True)
role = models.CharField(max_length=50, choices=Roles, default='client')
feedback = models.TextField(default=None, null=True)
def __str__(self):
return self.user.username
view for feedback:
#login_required
def feedback(request):
form = FeedbackForm()
if request.method =='POST':
form = FeedbackForm(request.POST)
if form.is_valid():
form = FeedbackForm.save()
return render(request, 'NewApp/feedback.html',{'form':form})
forms.py
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput())
confirm_password = forms.CharField(widget=forms.PasswordInput())
class Meta():
model = User
fields = ('first_name','last_name','username','email','password')
def clean(self):
cleaned_data = super(UserForm, self).clean()
password = cleaned_data.get("password")
confirm_password = cleaned_data.get("confirm_password")
if password != confirm_password:
raise forms.ValidationError(
"password and confirm_password does not match"
)
class UserProfileInfoForm(forms.ModelForm):
class Meta():
model = UserProfile
fields = ('role',)
class FeedbackForm(forms.ModelForm):
class Meta():
model = UserProfile
fields = ('feedback',)
The user on the UserProfile model can be nullable on database.
user = models.OneToOneField(User,on_delete=models.CASCADE, default=None, null=True)
And the method str is using this user to print the username
def __str__(self):
return self.user.username
You need to check if the user is none:
def __str__(self):
if self.user:
return self.user.username
return '{} {}'.format(self.__class__.__name__, self.pk)
I am trying to update UserDetailsSerializer and the problem is when I run my code in my test model it works but when I use it in my actual app, it throws this error: 'User' object has no attribute 'userprofile'
model.py
class userProfileModel(models.Model):
GENDER = [
('', ""),
('M', "Male"),
('F', "Female")
]
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='userprofile', default='')
avatar = models.ImageField(upload_to='avatar/', default='avatar/no-avatar.png')
age = models.DateField(auto_now_add=True)
gender = models.CharField(max_length=10, choices=GENDER, default='')
address = models.CharField(max_length=500, default='')
longitude = models.FloatField(default=0.0)
latitude = models.FloatField(default=0.0)
phone = models.IntegerField(default=0)
user_is_active = models.BooleanField(default=False)
def __str__(self):
return self.user.username
serializers.py
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = userProfileModel
fields = (
'id',
'avatar',
'age',
'gender',
'address',
'longitude',
'latitude',
'phone',
)
class UserDetailsSerializer(UserDetailsSerializer):
profile = UserProfileSerializer(source='userprofile')
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('profile',)
read_only_fields = ('',)
def update(self, instance, validated_data):
# User data
nested_serializer = self.fields['profile']
nested_instance = instance.userprofile
nested_data = validated_data.pop('userprofile')
nested_serializer.update(nested_instance, nested_data)
return super(UserDetailsSerializer, self).update(instance, validated_data)
The error:
RelatedObjectDoesNotExist at /rest-auth/user/
User has no userprofile.
It's because UserProfile instance for user is not created yet. You can use a signal on post_save of User model, so that whenever user is saved, check if UserProfile instance for it is created, if not then create one. like below:
def create_profile(sender,**kwargs ):
if kwargs['created']:
user_profile=UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile,sender=User)
Also like my friend suggested in comments, Always check if userProfile instance is exist with hasattr(instance, 'profile'), if not then create a default one for that user.
Reference: This is a cool topic Extending user model
When you are creating user profile, make sure you save them, you can refer this snippet.
class Profile(models.Model):
# ...
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
And also don't forget to import post_save.
I have a registration form which is an extension of UserCreationForm and I have a UserProfileForm. I am rendering both the forms to the same html during user registration.
The problem is, inputs are getting saved to the inbuilt users model but not to the Profile Model. No data is showing up in Profile Model.
I have tried many ways and looked for many solution but unable to find the mistake.
My forms.py looks like this -
class RegistrationForm(UserCreationForm):
email = forms.EmailField(required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
username = forms.CharField(required=True)
class Meta:
model = User
fields = ['username',
'first_name',
'last_name',
'email',
'password1',
'password2'
]
def save(self,commit):
user = super(RegistrationForm,self).save(commit=False)
if commit:
user.save()
return user
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ['user']
def save(self,commit):
user = super(RegistrationForm,self).save(commit=False)
USN =self.cleaned_data['USN']
year = self.cleaned_data['year']
sem = super(RegistrationForm,self).save(commit=False)
if commit:
user.save()
return user
Have created UserProfile model in models.py
class UserProfile(models.Model):
alphanumeric = RegexValidator(r'^[0-9A-Z]*$', 'Only alphanumeric
characters are allowed.')
user = models.OneToOneField(User, related_name =
'profile',on_delete=models.CASCADE)
USN = models.CharField(max_length=50, blank=True, null=True, validators=
[alphanumeric])
year = models.IntegerField(default=0)
sem = models.IntegerField(default=0)
def __str__(self):
return self.user.username
def create_profile(sender, **kwargs):
if kwargs['created']:
objects=models.Manager()
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender = User)
I have used both the forms in Views.py -
def register(request):
if request.method == 'POST':
form_1 = RegistrationForm(request.POST)
form_2 = UserProfileForm(request.POST)
if form_1.is_valid() and form_2.is_valid():
save_1 = form_1.save(commit = False)
save_2 = form_1.save(commit = False)
save_1.save()
save_2.save()
return render(request,'main/home.html')
else:
form_1 = RegistrationForm()
form_2 = UserProfileForm()
args = {'form_1':form_1,'form_2':form_2}
return render(request, 'account/register.html',args)
The username in the profile model object shows correctly, but other fields are not getting updated. I want other fields also getting updated in the profile object.
I am trying to extend the Django rest framework (version 3.x.x) with gender, created_at, updated_at fields that are defined in a separate model called UserProfile. When I try to update the UserProfile model instance including the nested User model instance, it updates only the UserProfile instance (gender, updated_at fields). So basically what I want to achieve is to be able to update also the email, first_name and last_name fields from the User model
models.py:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key = True, related_name = 'profile')
gender = models.CharField(choices = GENDERS, default = 2, max_length = 64)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
def __unicode__(self):
return self.user.username
#receiver(post_save, sender = User)
def create_profile_for_user(sender, instance = None, created = False, **kwargs):
if created:
UserProfile.objects.get_or_create(user = instance)
#receiver(pre_delete, sender = User)
def delete_profile_for_user(sender, instance = None, **kwargs):
if instance:
user_profile = UserProfile.objects.get(user = instance)
user_profile.delete()
serializers.py:
class UserProfileSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(source = 'pk', read_only = True)
username = serializers.CharField(source = 'user.username', read_only = True)
email = serializers.CharField(source = 'user.email')
first_name = serializers.CharField(source = 'user.first_name')
last_name = serializers.CharField(source = 'user.last_name')
class Meta:
model = UserProfile
fields = (
'id', 'username', 'email', 'first_name', 'last_name',
'created_at', 'updated_at', 'gender',
)
read_only_fields = ('created_at', 'updated_at',)
def update(self, instance, validated_data):
#user = User.objects.get(pk = instance.user.pk);
user = instance.user
user.email = validated_data.get('user.email', user.email)
user.first_name = validated_data.get('user.first_name', user.first_name)
user.last_name = validated_data.get('user.last_name', user.last_name)
user.save()
instance.gender = validated_data.get('gender', instance.gender)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
profile = UserProfile.objects.create(user = user, **validated_data)
return profile
views.py:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
Here's how I did it:
def update(self, instance, validated_data):
# First, update the User
user_data = validated_data.pop('user', None)
for attr, value in user_data.items():
setattr(instance.user, attr, value)
# Then, update UserProfile
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Basically, I looked into the original code for serializers (here, line 800) and modified it so that it would fit my needs.
Good luck.
A slight modification to the above code replacing the None with {}
Because 'NoneType' object has no attribute 'items'
def update(self, instance, validated_data):
# First, update the User
user_data = validated_data.pop('user', {})
for attr, value in user_data.items():
setattr(instance.user, attr, value)
# Then, update UserProfile
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
There is another way that I am implementing which takes care of validation. I am implementing it for handling PATCH
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
class ProfileSerializer(serializers.ModelSerializer):
email = serializers.CharField(source='user.email')
first_name = serializers.CharField(source='user.first_name')
last_name = serializers.CharField(source='user.last_name')
class Meta:
model = Profile
exclude = ('user',)
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user_serializer = UserSerializer(instance.user, data=user_data, partial=True)
user_serializer.is_valid(raise_exception=True)
user_serializer.update(instance.user, user_data)
super(ProfileSerializer, self).update(instance, validated_data)
return instance