How to add extra field to django serializer? - django

I am trying to add an additional field to my serializer but I am getting the following error:-
"The field 'provider' was declared on serializer CreateUserSerializer, but has not been included in the 'fields' option."
Here is my serializer.py:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
username = serializers.CharField()
company = serializers.CharField()
provider = serializers.CharField()
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
def get_company_detail(self):
return {}
def get_branch_detail(self):
return {}
def create(self, validated_data):
try:
with transaction.atomic():
user = User.objects.create(**validated_data)
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=validated_data['provider'])
company = CompanyModel.objects.create(user=user, name=validated_data['company'])
branch = BranchModel.objects.create(user=user, name=validated_data['company'], company=company)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
I don't want to add the company and provider fields in the field option as it is not a part of user model. I just want to use them as writable fields so that I can create object for the two models.
How can I get rid of the following error?

I think you can't use that field without adding it to fields. What you can do is simply split model & extra fields into two lists and then define:
provider = serializers.CharField()
company = serializers.CharField()
...
class Meta:
model = User
model_fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company_detail', 'branch_detail']
extra_fields = ['company', 'provider']
fields = model_fields + extra_fields

I found an alternative to solve this problem:-
class CreateUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField()
company = serializers.CharField(write_only=True)
provider = serializers.ChoiceField(write_only=True, choices=OAUTH_PROVIDER_CHOICES)
password = serializers.CharField(write_only=True)
company_detail = serializers.SerializerMethodField()
branch_detail = serializers.SerializerMethodField()
# to get the details of company in response body
def get_company_detail(self, obj):
return {}
# to get the details of branch in response body
def get_branch_detail(self, obj):
return {}
def create(self, validated_data):
# pop additional fields from validated data to avoid error
company_name = validated_data.pop('company', None)
provider = validated_data.pop('provider', None)
try:
with transaction.atomic():
# create a user object
user = User.objects.create(**validated_data)
# create a user profile object
user_profile = UserProfileModel.objects.create(user=user)
user_profile.__dict__.update(**validated_data)
user_profile.save()
# create federated identity object using provider and email
identity = FederatedIdentityModel.objects.create(user=user, oauth_provider=provider,
email=validated_data['email'])
# create company object
company = CompanyModel.objects.create(user=user, name=company_name)
return user
except APIException:
raise APIException(
detail="Failed to register",
code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class Meta:
model = User
fields = ['first_name', 'last_name', 'password', 'email', 'username',
'company', 'provider', 'company_detail', 'branch_detail']
while creating user, I am popping the extra fields and using the popped value as the value while creating specific objects and adding the write_only=True in fields.
In that case, I can add the fields in my field_list without getting any error

Related

Django rest framework: mixing some fields of one model with another and make a serializer

I have the User model and UserProfile model.
User model contains the following:
email
password
first_name
last_name
and
UserProfile model contains
class UserProfile(models.Model):
user = models.OneToOneField(on_delete=models.CASCADE, primary_key=True)
photo = models.ImageField()
location = models.TextField()
phone = models.CharField(max_length=15)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I want to create an api endpoint where User can edit the following fields and save after login
--> from User
first_name
last_name
--> from Userprofile
photo
location
phone
In normal Django forms. I can use multiple forms and validate them and save. User will see them as one form.
Eg:
if request.POST():
formA = UserForm(instance=request.user)
formB = UserProfileForm(instance=request.user.userprofile_set)
a_valid = formA.is_valid()
b_valid = formB.is_valid()
# we do this since 'and' short circuits and we want to check to whole page for form errors
if a_valid and b_valid:
a = formA.save()
b = formB.save()
In serializers how can I do this
You can do something like this:
UserSerializer
class UserSerializer(serializer.ModelSerializer):
class Meta:
model = User
fields = ['first_name', 'last_name']
def update(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.save()
return instance
UserProfileSerializer
class UserProfileSerializer(serializer.ModelSerializer):
class Meta:
model = UserProfileSerializer
fields = ['photo', 'location', 'phone']
def update(instance, validated_data):
instance.photo = validated_data.get('photo', instance.photo)
instance.location = validated_data.get('location', instance.location)
instance.phone = validated_data.get('phone', instance.phone)
instance.save()
return instance
Then create a view like this which receives data in this format:
{
user: {first_name: "", last_name: ""},
profile:{photo: "", location: "", phone: ""}
}
The view
class UserDetailUpdateView(APIView):
def post(self, request, username):
user_data = request.data['user']
profile_data = request.data['profile']
user = get_object_or_404(User, username=username)
serialized_user = UserSerializer(user, data=user_data, partial=True)
serialized_profile = UserProfileSerializer(user.userprofile, data=profile_data, partial=True)
if serialized_user.is_valid() and serialized_profile.is_valid():
serialized_user.save()
serialized_profile.save()
return Response('Updated', status=status.HTTP_200_OK)
return Response('Error occurred', status=status.HTTP_400_BAD_REQUEST)

How to double check password in serializer of django restframework?

I want to double check password and repeated_password sent from front-end.
{"user_data":{"user_name":"jim", "email":"jim#google.com", "password":"ddd","repeat_password":"ssss","role_list":[1,2,3]}}
And I add a validator in my serilazer as :
# serilazers.py
class SUser(serializers.ModelSerializer):
name = serializers.SerializerMethodField(read_only=True)
repeat_password = serializers.CharField(read_only=True)
role_list = serializers.SerializerMethodField(read_only=True)
def get_name(self, obj):
return obj.user_name
def get_role_list(self, obj):
role_queryset = MRole.objects.filter(group__in=obj.groups.all())
return [
{
"role_id": role.id,
"role_name": role.name,
}
for role in role_queryset
]
def validate(self, attrs):
print(attrs)
# OrderedDict([('user_name', 'jim'), ('email', 'jim#163.com'), ('password', 'ddd')]) there's only password here ,
# why repeat_password not appear?
if value.get('password') == value.get('repeat_password'):
return value
raise exceptions.ValidationError('need same passwd')
class Meta:
model = MUser
fields = ['id', 'name', 'user_name',
'email', 'password', 'repeat_password',
'groups', 'is_active',
'role_list']
# models.py
class Muser(AbstractUser):
user_name = models.CharField(max_length=150,unique=False)
email = models.EmailField(unique=True, blank=True)
class Meta:
db_table = 'User'
verbose_name_plural = verbose_name = 'user'
to validate data
user_data = request.data.get("user_data")
serializer_for_user = SUser(data={
**user_data
})
serializer_for_user.is_valid(raise_exception=True)
But when I try to validate the data, I can't get repeat_password in my validate method of serilazer.
How can I do my double check work of pwd?
Thanks.
try removing read_only=True
repeat_password = serializers.CharField()
In your case, I think you should go for Serializer, not ModelSerializer because toggling the value of read_only will always give you errors.
If you try with read_only=True then the field will be ignored.
If you try it with read_only=False then it will try to save that in your database and will raise the error that this field does not exist in the database.
Read-only fields are included in the API output, but should not be
included in the input during create or update operations. Any
read_only fields that are incorrectly included in the serializer
input will be ignored.
Set this to True to ensure that the field is used when serializing a
representation, but is not used when creating or updating an instance
during deserialization.
https://www.django-rest-framework.org/api-guide/fields/#read_only

DRF: validate a field from another table

I have created an API to upload the file. Now I want to add few checks before user can upload it. So in payload I am asking his email and token to validate him.
Now email and token are in separate table. How can I validate them. I am getting errors like
TypeError: 'email' is an invalid keyword argument for this function
my models file
class File(models.Model):
filename = models.FileField(blank=False, null=False,upload_to='files')
remark = models.CharField(max_length=20)
timestamp = models.DateTimeField(auto_now_add=True)
my serializer file
class FileSerializer(serializers.ModelSerializer):
token = serializers.CharField(label=_("Token"))
email = serializers.CharField(label=_('email'))
def validate(self, attrs):
print("validating params")
token = attrs.get('token')
email= attrs.get('email')
validate(token, email)
return attrs
class Meta():
model = File
fields = ('filename', 'remark', 'timestamp', 'token', 'email')
read_only_fields = ('token', 'email')
you only want email and token when creating a file(uploading) and also they are not fields in your model, so you should make them write_only and also you should override the create method in serializers and pop them out before saving into the model.
class FileSerializer(serializers.ModelSerializer):
token = serializers.CharField(label=_("Token"), write_only=True)
email = serializers.CharField(label=_('router_macid'), write_only=True)
def validate(self, attrs):
print("validating params")
token = attrs.get('token')
email= attrs.get('email')
validate(token, email)
return attrs
def create(self, validated_data):
validated_data.pop('email', None)
validated_data.pop('token', None)
return super().create(validated_data)
class Meta():
model = File
fields = ('filename', 'remark', 'timestamp', 'token', 'email')

DRF - Filters in ModelSerializer

In my models an User is related with its Profile, which has Companies. In order to serialize them I want that if the user who makes the request is_staff, then the serializer must return all the companies, not only the ones s/he has through the relationship in the model.
What's the proper way to...:
1) Check if the user is staff.
2) Return all the companies if user is staff or return the companies of the profile related with the user.
I guess the best way to deal with this would be check in BProfileSerializerRelated if the user is staff and then add something like this:
company = serializers.SerializerMethodField('get_companies')
def get_companies(self, obj):
companies = Company.objects.all()
serializer = CompanySerializer(instance=companies, many=True)
return serializer.data
My current code doesn't take into account if the user is staff so return just the companies related with that user:
models.py
class BUser(AbstractBaseUser, PermissionsMixin):
username = models.CharField(max_length=40, unique=True)
first_name = models.CharField(max_length=40)
last_name = models.CharField(max_length=40)
class BProfile(models.Model):
user = models.ForeignKey('BUser')
company = models.ForeignKey('BCompany')
groups = models.ManyToManyField(Group)
class BCompany(models.Model):
name = models.CharField(max_length=64)
dealer = models.ForeignKey(BProfile, related_name='companies', blank=True, null=True)
views.py
#api_view(['GET'])
def current_user_detail(request):
serializer = BUserSerializerRelated(request.user)
return Response(serializer.data)
serializers.py
class BUserSerializerRelated(serializers.ModelSerializer):
bprofile_set = BProfileSerializerRelated(many=True)
class Meta:
model = BUser
fields = ('id', 'bprofile_set', 'username', 'email', 'first_name', 'last_name')
class BProfileSerializerRelated(serializers.ModelSerializer):
company = CompanySerializer()
groups = GroupSerializer(many=True)
dealer_companies = CompanySerializer(many=True)
class Meta:
model = BProfile
fields = ('id', 'dealer_companies', 'company', 'groups')
class CompanySerializer(serializers.ModelSerializer):
sites = SiteSerializer(many=True)
services = ServiceSerializer(many=True)
class Meta:
model = Company
fields = ('id', 'dealer','name', 'cif', 'sites', 'services')
Serializer has request object in his context dict. So you can get the current user from there.
def get_companies(self, obj):
user = self.context['request'].user
if user.is_staff:
serializer = CompanySerializer(Company.objects.all(), many=True)
else:
serializer = CompanySerializer(instance=companies, many=True)
return serializer.data
To access request object in serializer you need to pass it context dict while initializing serializer i.e in your views.py
serializer = BUserSerializerRelated(request.user, context={'request': self.request})
then in your serializers.py, you can access request user object like
user = self.context['request'].user

how to define a userprofile into UserDetialsSerializer?

I want to be able to access a userprofile instance through :
profile = instance.userprofile statement in UserSerializer
instance is created through:
instance = super(UserSerializer, self).update(instance, validated_data) statement in
UserSerializer
Since UserSerializer is inheriting UserDetailsSerializer, i think i should define a userprofile in UserDetailsSerializer.
But i dont know how to do it ?
Question: How to define userprofile in UserDetailsSerializer to achieve the above ?
UserSerializer:
class UserSerializer(UserDetailsSerializer):
company_name = serializers.CharField(source="userprofile.company_name")
class Meta(UserDetailsSerializer.Meta):
fields = UserDetailsSerializer.Meta.fields + ('company_name',)
def update(self, instance, validated_data):
profile_data = validated_data.pop('userprofile', {})
company_name = profile_data.get('company_name')
instance = super(UserSerializer, self).update(instance, validated_data)
# get and update user profile
profile = instance.userprofile
if profile_data and company_name:
profile.company_name = company_name
profile.save()
return instance
UserDetailsSerializer:
class UserDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ('username','email', 'first_name', 'last_name')
read_only_fields = ('email', )
UserProfile model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
# custom fields for user
company_name = models.CharField(max_length=100)
Do ask if more clarity is required?
I think you want a serializer methodfield to be part of your serializer? (I don't full understand your question);
class UserDetailsSerializer(serializers.ModelSerializer):
user_related = serializers.Field(source='method_on_userprofile')
class Meta:
model = UserProfile
fields = ('username','email', 'first_name', 'user_related', )
read_only_fields = ('email', 'user_related',)
I think I have answered similar one here
In the documentation it is assumed that userprofile was already created and now can be updated. You just need a check
# get and update user profile
try:
profile = instance.userprofile
except UserProfile.DoesNotExist:
profile = UserProfile()
if profile_data and company_name: