Django multiple serializers with relation - django

I have two tables of users and I am creating a form to store the user information.
Models.py
class MyUser(User):
address = models.CharField(max_length=200)
city = models.CharField(max_length=200)
expire_date = models.DateField()
This creates a table with user_ptr_id to the auth_user table of django.
I created two serializers: one for the User:
class UserSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(min_length=2, required=True)
last_name = serializers.CharField(min_length=2, required=True)
email = serializers.EmailField(min_length=5, required=True)
password = serializers.CharField(min_length=8, write_only=True, required=True)
class Meta:
model = User
fields = ('email', 'first_name', 'last_name', 'password')
def create(self, validated_data):
return UserSerializer(**validated_data)
And MyUser class:
class MyUserSerializer(serializers.ModelSerializer):
address = serializers.CharField(max_length=200)
city = serializers.CharField(max_length=200)
class Meta:
model = MyUser
fields = ('city', 'address')
def create(self, validated_data):
return MyUser(**validated_data)
As I am using Django Rest-Framework-Auth, I craeted a serializer to catch the data, but I don't know how to let the things work together. In the "MyUserSerializer" class, I also perform many validate checks, that I omitted here to keep the code clean.
Code below doesn't work
class UserSignup(serializers.Serializer):
user = UserSerializer()
my_user = MyUserSerializer()
confirm_psw = serializers.CharField(min_length=8, write_only=True, required=True)
def validate(self, data):
if not data["user"].get('password') or not data.get('confirm_psw'):
raise serializers.ValidationError("Please enter a password and confirm it.")
if data["user"].get('password') != data.get('confirm_psw'):
raise serializers.ValidationError("Those passwords don't match.")
return data
def save(self, validated_data):
user_data = self.validated_data["user"]
my_user = self.validated_data["my_user"]
return user_data, my_user
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(user_data)
my_user = validated_data.pop('my_user')
my_user["user_ptr_id"] = user.id
MyUser.objects.create(**ua_user)
return user
How do I merge the two serializers to correctly perform the user registration?
Thanks

Related

"This field is required." in DRF serializers

I am facing an issue in DRF serializers. I have a model named Issue which has a foreign Key of User Model to save the user who has created the particular Issue. Now the Get request is working fine when I request to get the issues I get it perfectly fine with username who has created the issue, but when I do the post request I get the error on "created_by" field that "This field is requied" even though I am providing this field.
Following is my code:
Model
class Issues(models.Model):
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='issue_created_by')
title = models.CharField(max_length=225, default='', null=False)
details = models.CharField(max_length=1000, default='')
created_on = models.DateField(default=timezone.now)
tags = models.CharField(max_length=225, blank=True, null=True, default='')
Issue Serializer
class IssueSerializer(serializers.ModelSerializer):
created_by = UserSerializer()
class Meta:
model = Issues
fields = ['created_by', 'title', 'details', 'created_on', 'tags']
UserSerializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name', 'email', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = super().create(validated_data)
user.set_password(validated_data['password'])
user.save()
return True
views.py
class IssueView(viewsets.ViewSet):
def create(self, request):
serialized_issues = IssueSerializer(data=request.data)
if serialized_issues.is_valid():
serialized_issues.save()
return Response({'message': 'Issue Created', 'status': status.HTTP_200_OK})
else:
return Response({'error': serialized_issues.errors, 'status': status.HTTP_400_BAD_REQUEST})
def list(self, request):
all_issues = Issues.objects.all()
serialized_issues = IssueSerializer(all_issues, many=True)
return Response(serialized_issues.data)
To work with a writeable nested serializer you need to overwrite the create method of the serializer. You can see the documentation [here]: https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
It seems you are creating a new user with every Issue created. It is quite unrealistic. You need to pass the request.user object to the serializer while creating a new issue. The updated code may be like
method in view
def create(self, request, *args, **kwargs):
created_by = request.user
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save(created_by=created_by)
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(data=serializer.errors, status=status.HTTP_400_BAD_REQUEST)```
and serializer file may be like
class IssueSerializer(serializers.ModelSerializer):
class Meta:
model = Issues
fields = ['title', 'details', 'created_on', 'tags']

How to update fields of custom user model using Django serializer

I am trying to update the various fields of a user model when the user wants to update it which was created earlier. I am having a custom user model and I am using django rest framework for the update api.
views.py
class UserUpdate(generics.UpdateAPIView):
"""
Update user.
"""
parser_class = (FileUploadParser,)
permission_classes = (AllowAny,)
queryset = User.objects.all()
serializer_class = UserUpdateSerializer
def update(self, request, *args, **kwargs):
instance = self.get_object()
instance.user_id = request.data.get("user_id")
instance.save()
serializer = self.get_serializer(instance, data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
models.py
class User(models.Model):
USER_CHOICES = (
(1, u'ADMIN'),
(2, u'SALES'),
(3, u'KITCHEN'),
(4, u'EMPLOYEE'),
)
image = models.ImageField(upload_to='employees/', null = True, blank = True)
name = models.CharField(max_length=50)
user_id = models.CharField(max_length=30, primary_key = True, blank = False)
email = models.CharField(max_length=50)
password = models.CharField(max_length=100)
is_active = models.BooleanField(default=True)
user_group = models.PositiveSmallIntegerField(default=4, choices=USER_CHOICES)
firebase_token = models.CharField(max_length=150, default=None, null = True)
shop = models.ForeignKey(Shop, on_delete=models.SET_DEFAULT, default=None)
phone = PhoneField(blank=False, default=0)
serializers.py
class UserUpdateSerializer(serializers.ModelSerializer):
shop = serializers.CharField()
class Meta:
model = User
fields = ('image', 'url', 'phone', 'name', 'user_id','email', 'password', 'is_active', 'user_group', 'shop')
def update(self, instance, validated_data):
shop = validated_data.pop('shop')
user_id = validated_data.pop("user_id")
print(user_id)
shopId = Shop.objects.filter(name=shop).values('shop_id').first()
if shopId is not None:
shop_id = shopId['shop_id']
try:
if user_id is not None:
instance.name = validated_data.get('name', instance.name)
instance.image = validated_data.get('image', instance.image)
instance.email = validated_data.get('email', instance.email)
instance.phone = validated_data.get('phone', instance.phone)
instance.password = validated_data.get('password', instance.password)
instance.user_group = validated_data.get('user_group', instance.user_group)
instance.shop_id = validated_data.get('shop_id', instance.shop_id)
instance.is_active = True
instance.save()
return instance
else:
print("Test")
except Exception as e:
return e
else:
return None
This is throwing an error saying that the user already exists!
Please assist!
Just Change your serializer like below
class UserUpdateSerializer(serializers.ModelSerializer):
shop = serializers.CharField()
class Meta:
model = User
fields = ('image', 'url', 'phone', 'name', 'user_id','email', 'password', 'is_active', 'user_group', 'shop')
def update(self, instance, validated_data):
shop = validated_data.pop('shop')
shop_obj = Shop.objects.filter(name=shop).first()
if shop_obj:
instance.shop = shop_obj
return super().update(instance, validated_data)
When you are calling UserUpdate API, it will create every new User for every new user_id.
I don't know why you are updating user_id. Any new user_id will create a new User instance.
If you want to update other than user_id, you need to remove the following line.
Remove these lines from UserUpdate API View.
instance.user_id = request.data.get("user_id")
instance.save()
Remove this line from UserUpdateSerializer
instance.user_id = validated_data.get('user_id', instance.user_id)
Add lookup_field = 'user_id' to UserUpdate API view
Add user_id at url, like
path('userupdate/<str:user_id>/', UserUpdate.as_view())

DRF Foreignkey serialization

I can't save model with Foreignkey field.
Thanks to "azudo" problem solved. Solution below
For example I have simple models:
class User(AbstractUser):
class Meta:
pass
email_validator = EmailValidator()
username = models.CharField('Name', max_length=150, )
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField('Email', blank=True, unique=True, validators=[email_validator], )
...
class Package(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='packages')
description = models.CharField('Description', max_length=256, default='description')
weight = models.CharField('weight', max_length=256, default='weight')
...
View (the user is guaranteed to be in the request):
#api_view(["POST"])
def test(request):
data = request.data
data['user'] = User.objects.get(id=request.user.id)
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
My serializers:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PackageSerializer(ModelSerializer):
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def to_representation(self, instance):
self.fields['user'] = UserSerializer(many=False, read_only=True)
self.fields['where_from'] = LocationSerializer(many=False, read_only=True)
self.fields['destination'] = LocationSerializer(many=False, read_only=True)
return super().to_representation(instance)
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user, **validated_data)
return package
json in request:
{
"description": "Some package",
"weight": "12",
}
So, I'have user in database, and want create package for him. But in overridden create in PackageSerializer, validated_data doesn't have user. Please explain what I'm doing wrong.
Versions of django and drf:
django==2.2.4
djangorestframework==3.10.2
Solution:
Serializer:
class PackageSerializer(ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = Package
fields = (
'user', 'description', 'weight', 'dimensions', 'estimated_shipping_cost', 'deliver_to_date')
def create(self, validated_data):
user = User.objects.get(validated_data.pop('user'))
package = Package.objects.create(user=user)
return package
View:
#api_view(["POST"])
def create_package(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)
DRF will ignore included fields that are marked as read-only so the caller cannot include read-only data. If you want to include additional attributes simply pass them as keyword args to save:
https://www.django-rest-framework.org/api-guide/serializers/#passing-additional-attributes-to-save
e.g.
#api_view(["POST"])
def test(request):
data = request.data
serializer = PackageSerializer(data=data)
if serializer.is_valid():
serializer.save(user=request.user)
return JsonResponse(serializer.data)
else:
return JsonResponse(serializer.errors)

Save two model instances in one updateview

Am trying to update the User model and UserProfile model in one view but it's not working. No error is shown and no changes are made to the objects. What am I not doing right.
Here is my models.py:
class UserProfile(models.Model):
"""User information not related to authentication"""
user = models.OneToOneField(User, related_name='user_profile', on_delete=models.CASCADE)
age = models.IntegerField()
# other fields ignored
Here is my serializer.py:
class UserSerializer(ModelSerializer):
first_name = CharField(max_length=20)
last_name = CharField(max_length=20)
email = EmailField(required=True, validators=[UniqueValidator(queryset=User.objects.all())])
username = CharField(max_length=32,validators=[UniqueValidator(queryset=User.objects.all())])
password = CharField(min_length=8, write_only=True)
confirm_password = CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create_user(
validated_data['username'],
email = validated_data['email'],
first_name = validated_data['first_name'],
last_name = validated_data['last_name']
)
password = validated_data['password']
confirm_password = validated_data['confirm_password']
if password != confirm_password:
raise ValidationError({'password': 'Passwords must match'})
else:
user.set_password(password)
user.save()
return user
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password', 'confirm_password')
class UserProfileSerializer(ModelSerializer):
username = CharField(source='user.username')
first_name = CharField(source='user.first_name')
last_name = CharField(source='user.last_name')
email = CharField(source='user.email')
class Meta:
model = UserProfile
exclude = ('user',)
# fields = '__all__'
# depth = 1
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data.get('username', instance.user.username)
instance.user.email = validated_data.get('email', instance.user.email)
instance.user.first_name = validated_data.get('first_name', instance.user.first_name)
instance.user.last_name = validated_data.get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance
Here is view.py:
class UserProfileUpdate(UpdateAPIView):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
lookup_field = 'user'
#Eric
Try changing your update method to this, the actual update data is under validated_data['user']
def update(self, instance, validated_data):
user = instance.user
instance.user.username = validated_data['user'].get('username', instance.user.username)
instance.user.email = validated_data['user'].get('email', instance.user.email)
instance.user.first_name = validated_data['user'].get('first_name', instance.user.first_name)
instance.user.last_name = validated_data['user'].get('last_name', instance.user.last_name)
instance.save()
user.save()
return instance

None model related field as SlugRelatedField to Serializer

I've added a view to create a new user, this takes a username, password, email and a slug field to link to a permission.
{
"username" : "TestUsername",
"email" : "TestUsername#outlook.com",
"password" : "Password01",
"group" : "partial-permission"
}
The view for this request is;
class CreateUserSerializer(serializers.ModelSerializer):
group = serializers.SlugRelatedField(queryset=CompanyGroup.objects.all(), slug_field='slug_field')
class Meta:
model = User
fields = ['company', 'email', 'username', 'password', 'token', 'group']
read_only_fields = ['token']
write_only_fields = ('password',)
def create(self, validated_data):
return User.objects.create_user(**validated_data)
I'm trying to use the SlugRelatedField to link automatically to the Group passed in the slug field and pass this onto my create_user method in my model.
class UserManager(BaseUserManager):
def get_queryset(self):
return UserQuerySet(self.model, using=self._db).active_and_not_deleted()
def create_user(self, username, email, password, group=None, company=None):
user = self.model(username=username, email=self.normalize_email(email), company=company)
user.set_password(password)
return user
When doing this I'm getting the exception:
AttributeError at /users/
Got AttributeError when attempting to get a value for field `group` on serializer `CreateUserSerializer`.
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 'group'.
I understand that this exception explains exactly what my problem is, but I'm trying to avoid having it on the User object and manually looking up the Group from the objects.
Edit:
Models as request;
class User(AbstractUser):
# Date the User was created
created_at = models.DateTimeField(auto_now_add=True)
# Date the User info was last updated
updated_at = models.DateTimeField(auto_now=True)
# Date the User last logged into the app
last_active = models.DateTimeField(auto_now=True)
# The company which this user is associated with.
company = models.ForeignKey(Company, on_delete=models.CASCADE, null=True)
# Indicates whether this user has been deleted or not
is_deleted = models.BooleanField(default=False)
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
objects = UserManager()
def __str__(self):
return self.username
#property
def token(self):
return self._generate_jwt_token()
def _generate_jwt_token(self):
dt = datetime.now() + timedelta(days=60)
token = jwt.encode({
'id': self.pk,
'exp': int(dt.strftime('%s'))
}, settings.SECRET_KEY, algorithm='HS256')
return token.decode('utf-8')
And group;
class CompanyGroup(Group):
slug_field = models.SlugField()
objects = GroupManager()
def __str__(self):
return self.name
I think there needs to have a ManyToMany Relation between User and GroupCompany(as far as I understood from comments). Because as per documentation:
SlugRelatedField may be used to represent the target of the relationship using a field on the target.
So, you can add it like this:
class User(AbstractUser):
# rest of the fields
group = models.ManyToManyField(GroupCompany)
Need to make sure, slug_field is unique in GroupCompany:
slug_field = models.SlugField(unique=True)
Also, you need to change the create_user model manager method as well:
def create_user(self, username, email, password, company=None):
user = self.model(username=username, email=self.normalize_email(email), company=company)
user.set_password(password)
user.save()
return user
And update the serializer as well:
class CreateUserSerializer(serializers.ModelSerializer):
group = serializers.SlugRelatedField(queryset=CompanyGroup.objects.all(), slug_field='slug_field', many=True) # added many True
def create(self, validated_data):
group = validated_data.pop('group')
user = User.objects.create_user(**validated_data)
user.group.add(group)
return user
Update
class CreateUserSerializer(serializers.ModelSerializer):
company_group = serializers.CharField(write_only=True) # added many True
def create(self, validated_data):
group = validated_data.pop('company_group')
user = User.objects.create_user(**validated_data)
group, _ = CompanyGroup.objects.create(slug_field=company_group)
user.group.add(group)
return user