I am struggling to understand why my app suddenly wont allow me to log in a user after any changes are made to their profile. I have a nested User serializer with the profile serializer fields (onetoOne) using Djoser for the urls. When I try to update the user profile from the api endpoint it updates but throws an error that the avatar has no file associated to it. I thought that if I added "required=False" to to the ProfileSerializer it would negate this behaviour. Please help it is driving me crazy, I have googled and not found the answer why. I think it is in my create method within my UserSerializer class. It is also not saving the avatar if any of the other fields are changed within the profile object. Very strange. It was all working fine and for some reason now its not logging in users.
Here is the model:
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
name = models.CharField(max_length=100, blank=True, null=True)
occupation = models.CharField(max_length=100, blank=True, null=True)
residence = models.CharField(max_length=100, blank=True, null=True)
email = models.CharField(max_length=100, blank=True, null=True)
active_id = models.BooleanField(default=True)
avatar = models.ImageField(null=True, blank=True, upload_to ='uploads/profile_pics/',default='uploads/default.jpg')
def __str__(self):
return self.user.username
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
img = Image.open(self.avatar.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.avatar.path)
Here is the serializers:
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = Profile
fields = ("__all__")
class ProfileStatusSerializer(serializers.ModelSerializer):
# user_profile = serializers.StringRelatedField(read_only=True)
class Meta:
model = ProfileStatus
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer(required=False, allow_null=True, partial=True)
parser_class = (FileUploadParser,)
class Meta:
model = User
fields = ['username', 'profile','password', 'id']
extra_kwargs = {"password":{'write_only': True}}
def update(self, instance, validated_data):
if 'profile' in validated_data:
nested_serializer = self.fields['profile']
nested_instance = instance.profile
nested_data = validated_data.pop('profile')
nested_serializer.update(nested_instance, nested_data)
return super(UserSerializer, self).update(instance, validated_data)
def create(self, validated_data):
return User.objects.create_user(
username = validated_data['username'], # HERE
password = validated_data['password'])
Please halp.
Solved.
I needed to add validated_data fields to the create method.
def create(self, validated_data):
return User.objects.create_user(
validated_data['username'],None,validated_data['password'])
Related
I am working on genericAPIViews in DRF. I am using a built in user model with UserProfile model having one to one relation with it. But I am unable to create user due to nested serializer. My question is that how I can create my built in User model and Profile User model at the same time as UserProfile model is nested in User model.Here is my code:
Models.py
USER_CHOICE = (
('SS', 'SS'),
('SP', 'SP')
)
LOGIN_TYPE = (
('Local', 'Local'),
('Facebook', 'Facebook'),
('Google', 'Google')
)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
cell_phone = models.CharField(max_length=15, blank=True, default="", null=True)
country = models.CharField(max_length=50, blank=True, default="", null=True)
state = models.CharField(max_length=50, blank=True, default="", null=True)
profile_image = models.FileField(upload_to='user_images/', default='', blank=True)
postal_code = models.CharField(max_length=50, blank=True, default="", null=True)
registration_id = models.CharField(max_length=200, null=True, blank=True, default=None)
active = models.BooleanField(default=True)
# roles = models.ForeignKey(Role, null=True, on_delete=models.CASCADE, related_name='role', blank=True)
user_type = models.CharField(max_length=50, choices=USER_CHOICE, null=True, blank=True)
login_type = models.CharField(max_length=40, choices=LOGIN_TYPE, default='local')
reset_pass = models.BooleanField(default=False)
confirmed_email = models.BooleanField(default=False)
remember_me = models.BooleanField(default=False)
reset_code = models.CharField(max_length=200, null=True, blank=True, default="")
reset_code_time = models.DateTimeField(auto_now_add=True, blank=True)
longitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
latitude = models.DecimalField(max_digits=80, decimal_places=10, default=0.00)
r_code = models.CharField(max_length=15, null=True, blank=True)
refer_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="user_refer")
referred = models.ManyToManyField(User, related_name="user_referred", null=True, blank=True)
otp = models.CharField(max_length=6, blank=True, default="", null=True)
def __str__(self):
return self.user.username
Seralizers.py
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import UserProfile
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = '__all__'
class UserSerializer(serializers.ModelSerializer):
profile = UserProfileSerializer()
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'profile']
Views.py
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminUser]
I know there is .create() method which can be override according to DRF documentation. Plus I want to override this method in views.py. Is there any approach to do this. Thanks in advance for your addition in my knowledge.
There are two ways of doing that in my knowledge.
The first one is overriding the create method of the generic view. Which is as follows:
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def create(self, request, *args, **kwargs):
#let django create the user with generic view
response = super().create(request, *args, **kwargs)
# response.data contains the serialized data of the created user
user_data = response.data
user_id = response.data.get("id")
profile = UserProfile()
profile.user = User.objects.get(id=user_id)
profile.save()
# return the response created by the generic view
return response
Be careful that Django Rest Framework does not support the create method for nested serializers. Set the profile field to read-only in UserSerializer
class UserSerializer(serializers.ModelSerializer):
profile = SerializerMethodField()
class Meta:
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'profile']
read_only_fields = ('profile',)
def get_profile(self, user):
profile = UserProfile.objects.filter(user=user).first()
return UserProfileSerializer(profile).data if profile is not None else None
The second way is using signals. Signals is an event dispatcher that keeps track of events such as update, delete, m2m changed, etc. Here, we can use post_save signals which trigger whenever a model gets updated. So if the User model is created, we will receive a signal and therefore crate a UserProfile for that user. You can learn more about signals from the Django Signals page.
#receiver(post_save, sender=User, dispatch_uid="create_user_profile")
def on_user_create(sender, instance, created, **kwargs):
# here instance is the user object that just got created
# created is a boolean value which will be true if the record is newly created
if created:
profile = UserProfile()
profile.user = instance
profile.save()
Im new to Django REST framework, but getting the hang of it. Im trying to make a serializer from the Profile Model but i dont know how to pass (def followers and following) into the serializer
This is the Profile Model:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.CharField(max_length=245, null=True)
image = models.ImageField(default='default.png', upload_to='profile_pics')
interests = models.ManyToManyField(Category, related_name='interests_user')
stripe_customer_id = models.CharField(max_length=50, blank=True, null=True)
one_click_purchasing = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.username} Profile'
#property
def followers(self):
return Follow.objects.filter(follow_user=self.user).count()
#property
def following(self):
return Follow.objects.filter(user=self.user).count()
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
super().save()
img = Image.open(self.image.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.image.path)
class Follow(models.Model):
user = models.ForeignKey(User, related_name='user', on_delete=models.CASCADE)
follow_user = models.ForeignKey(User, related_name='follow_user', on_delete=models.CASCADE)
date = models.DateTimeField(auto_now_add=True)
old_instance = models.ForeignKey('Follow', blank=True, null=True, on_delete=models.CASCADE, editable=False)
def save(self, *args, **kwargs):
if self.pk is not None:
self.old_instance = Follow.objects.get(pk=self.pk)
super().save(*args,**kwargs)
def __str__(self):
return f"For: {self.user} // id: {self.id}"
This is the serializer I built so far,
But it doesnot work correctly, Error: 'User' object has no attribute 'count (line 40)
class PublicProfileSerializer(serializers.ModelSerializer):
follow_user = serializers.SerializerMethodField(read_only=True)
followers = serializers.SerializerMethodField(read_only=True)
following = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Profile
fields = '__all__'
def get_follow_user(self, obj):
return obj.follow_user
def get_followers(self, obj):
# return obj.user.followers.count()
return FollowSerializer(obj.follow_user.count())
def get_following(self, obj):
# return obj.user.following.count()
return FollowSerializer(obj.user.count()) - Line 40
Use this in your serializer
class ExampleSerializer(serializers.ModelSerializer):
followers= serializers.CharField(source='followers') # the followers at source='follower should be the name of function'
following = serializers.CharField(source='following')
class Meta:
model = Example
fields = '__all__'
the lhs is the name you want to give to variable and the rhs is how you gonna get that value(in this case it's the functions)
Try this,
from rest_framework import serializer
class UserSerializer(serializers.ModelSerializer):
# hack..hack..
class FollowSerializer(serializers.ModelSerializer):
# hack..hack..
class ProfileSerializer(serializers.ModelSerializer):
followers = FollowSerializer(many=True, read_only=True)
following = FollowSerializer(many=True, read_only=True)
# hack..hack..
class Meta:
model = models.Profile
fields = (..., "followers", "following", )
Then you can use it with views, viewsets. If you want to use it manually, use it like this
# Single Instance
serializer = ProfileSerializer(single_instance)
serializer.data # This returns json
# Multiple Instances
serializer = ProfileSerializer(iterable_of_instances, many=True)
serializer.data # This returns json
I have some problem in uploading picture in user profile its already extended onetoone from users , when I wanted to upload pic via postman it returned null value
serializer :
class UserProfileSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = UserProfile
fields = ('user', 'bio', 'avatar')
def validate(self, data):
print(data)
self.context['avatar'] = self.context['request'].FILES.get('avatar')
return data
def create(self, validated_data):
user_data = validated_data.pop('user')
user = UserSerializer.create(UserSerializer(), validated_data=user_data)
validated_data['avatar'] = self.context['avatar']
validated_data['user'] = user
profile = UserProfile.objects.create(**validated_data)
profile.bio = validated_data["bio"]
profile.save()
return profile
The model:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, related_name='profile', on_delete=True)
bio = models.CharField(max_length=50, blank=True)
avatar = models.ImageField(upload_to="media", blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.user.username
i found the solution ,the picture in django store in memory at request.FILES
userprofile = UserProfile.objects.create(user=user, bio=validated_data.pop('bio'), user_type=type)
images_data = self.context.get('view').request.FILES
for image_data in images_data.values():
ProfileImage.objects.create(userprofile=userprofile, image=image_data, user_type=type)
Im trying to find a method for posting a string value and saving it to a foreign key field instead of using the pk.
My models:
class CustomUser(models.Model):
username = models.CharField(max_length=500, unique=True)
def __str__(self):
return self.username
class Order(models.Model):
ordernumber = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False)
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True, blank=True)
amount = models.FloatField(null=True, blank=True)
def __str__(self):
return "{0}".format(self.ordernumber)
And my serializer:
class OrderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = Order
fields = ('id','username', 'ordernumber', 'amount')
read_only_fields = ('id')
When using GET, everything works perfectly, but I can't seem to fix the POST method.
I tried to override the create method like this:
def create(self, validated_data):
username = validated_data.pop('username')
order = Order.objects.create(**validated_data)
order.user = CustomUser.objects.get(username=username)
order.save()
return order
But I get a KeyError on the username = validated_data.pop('username') line: Exception Value:'username'
When you use source with nested fields, data will be accessible as validated_data['parent_field']['child_field']. Try this:
def create(self, validated_data):
user_data = validated_data.pop('user')
username = user_data.pop('username')
order = Order.objects.create(**validated_data)
order.user = CustomUser.objects.get(username=username)
order.save()
return order
I'm trying to first access the users table via the user foreign key present in userinformations models and later override the RetriveUpdateDestroy API view's destroy method to change the status of the user to inactive instead of deleting them. I can't seem to access the is-active field of the in built User database.
views.py
class UserUpdateApiView(RetrieveUpdateDestroyAPIView):
queryset = UserInformation.objects.all()
serializer_class = UserInformationUpdateSerializer
lookup_field = 'pk'
lookup_url_kwarg = 'id'
def destroy(self, request, *args, **kwargs):
try:
user = User.objects.get(pk=self.kwargs["id"])
deleteStatusVal = False
user.is_active = deleteStatusVal
user.save()
return Response(UserSerializer(user).data)
except:
return Response("Nope")
serializers.py
class UserSerializer(ModelSerializer):
password = serializers.CharField(style={'input_type': 'password'}, write_only=True)
email = serializers.EmailField(validators=[required])
class Meta:
model = User
fields = ['username', 'email', 'password', 'is_active']
extra_kwargs = {'password': {'write_only': True},
'is_active': {'read_only': True}}
def validate(self, data):
email = data.get('email', None)
user = User.objects.filter(email=email).distinct()
if user.exists():
raise ValidationError("That email is already registered!")
return data
class UserInformationUpdateSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = UserInformation
fields = ['user', 'first_name', 'middle_name', 'last_name', 'phone', 'date_of_birth']
models.py
class UserInformation(BaseModel):
user = models.OneToOneField(User, related_name='user_id')
first_name = models.CharField(max_length=45)
middle_name = models.CharField(max_length=45, null=True)
last_name = models.CharField(max_length=45)
vendor = models.BooleanField(default=False)
phone = models.CharField(max_length=100, validators=[
RegexValidator(regex=r'^\+?8801?\d{9}$', message="Phone number must be entered in the format: '+8801*********'")
], blank=False, unique=True)
date_of_birth = models.DateField()
confirmation_token = models.CharField(max_length=45, null=True)
confirmation_exp = models.DateTimeField(null=True)
pw_reminder_token = models.CharField(max_length=45, null=True)
pw_reminder_exp = models.DateTimeField(null=True)
profile_pic = models.ImageField(blank=True, null=True, upload_to='profile_images/', default='Images/none/no_images.jpg')
cover_photo = models.ImageField(blank=True, null=True, upload_to='cover_images/', default='Images/none/no_images.jpg')
thumbnail_pic = models.ImageField(blank=True, null=True, upload_to='thumbnail_images/', default='Images/none/no_images.jpg')
phone_verified = models.BooleanField(default=False)
email_verified = models.BooleanField(default=False)
reward_points = models.IntegerField(null=False)
ref_code = models.CharField(null=True, max_length=10)
def __str__(self):
return self.user.username
def delete(self, *args, **kwargs):
self.user.delete()
super(UserInformation, self).delete(*args, **kwargs)
If you want to make User as in active while keeping the UserInformation object and Userobject un-deleted in database, you can do something like this:
def destroy(self, request, *args, **kwargs):
user = self.get_object().user
user.is_active = False
user.save()
return Response(UserInformationUpdateSerializer(self.get_object()).data)
You have 'is_active': {'read_only': True}}.
Also,
# this seems redundant
def delete(self, *args, **kwargs):
self.user.delete()
super(UserInformation, self).delete(*args, **kwargs)