So far I have ->
serializer:
class UserSerializer(serializers.ModelSerializer):
"""Serializer to map the model instance into json format."""
class Meta:
"""Map this serializer to a model and their fields."""
model = User
fields = ('id','username', 'mobile', 'password',
'first_name','last_name','middle_name',
'profile_pic','short_bio','friends_privacy',
'address_1','address_2','city',
'state','country','pin','verification_code',
'is_active','is_blocked','is_reported',
'date_created','date_modified')
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified',
'is_staff', 'is_superuser', 'is_active',
'date_joined',)
def create(self, validated_data):
mobile_ = validated_data['mobile']
password_ = validated_data['password']
username_ = validated_data['username']
motp = self.context['request'].GET['motp']
eotp = self.context['request'].GET['eotp']
email_ = self.context['request'].GET['email']
mflag = api.views.checkOTP_(mobile_,motp)
eflag = api.views.checkOTP_(email_,eotp)
if (mflag and eflag):
user = User(
username=username_,
email =email_,
password = make_password(password_),
mobile = mobile_,
)
user.set_password(validated_data['password'])
user.save()
return user
view:
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
model = User
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
I need to check for OTP of mobile and email and also if a user with same mobile or email already exists.
If user already exists return a json response with error: already exists!.
If user is new and OTP is wrong again raise an error.
If user is new and OTP is correct, create an account.
Problem here is I tried to add the function to check for otp verification inside the def create of UserSerializer. But a serializer is supposed to return the model instance. But if you see the code, I am able to create a user only if OTP is right and user instance is returned. And there is no else.
So is there a better way to check for OTP in the view itself?
I don't agree with #Anjaneyulu there..
Serializer handles creation of objects as well.. hence the reason you have serializer.save().
But for the purpose of raising an exception for existing user with same OTP email/phone, you should write your own def validate_mobile(self, data) and def validate_email(self, data). DRF serializer will look for these methods in the class first and will run them if they exist. So your custom logic for checking those fields could be:
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(mobile=value).exists():
raise serializers.ValidationError('already exists')
return value
def validate_email_(self, value):
ModelClass = self.Meta.model
if ModelClass.objects.filter(email_=value).exists():
raise serializers.ValidationError('already exists')
return value
class Meta:
model = User
fields = (
...,
)
That is not the correct way of implementing it. Serializers are meant only for validation purposes. you should not implement the create method in serializer instead write it in ViewSet. Creating object is a business logic. It should always go in a ViewSet. Write a validation method to the serializer. I'm writing an example code below
serializers.py
class UserSerializer(serializers.ModelSerializer):
def validate_mobile(self, mobile_num):
is_already_exists = Model.objects.filter(mobile=mobile_num).exists()
if is_already_exists:
raise serializers.ValidationError('already exists')
return mobile_num
class Meta:
model = User
fields = (
'id','username', 'mobile', 'password',
'first_name','last_name','middle_name','profile_pic',
'short_bio','friends_privacy','address_1',
'address_2','city','state','country',
'pin','verification_code','is_active',
'is_blocked','is_reported',
'date_created','date_modified'
)
extra_kwargs = {'password': {'write_only': True}}
read_only_fields = (
'date_created', 'date_modified','is_staff',
'is_superuser', 'is_active', 'date_joined',
)
Viewsets.py(Business Logic)
class UserView2(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
def create(self, serializer):
# your logic goes here.
Related
I serialize the field named "product" with ProductSerializer() inside OrderItemSerializer().
That's what I want.
class OrderItemSerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = models.OrderItem
fields = ('id','order', 'product', 'quantity')
The output is;
But when I try to request with POST Method needs to send Product as a dictionary, just giving the id value is not enough.
How can I POST by sending only the id value?
I haven't written anything about the operation yet. Default ModelViewSet
class OrderItemViewSet(ModelViewSet):
queryset = OrderItem.objects.all()
serializer_class = serializers.OrderItemSerializer
permission_classes = (IsOwnerOrNot, IsAuthenticated)
def get_queryset(self):
user = self.request.user
return self.filter_queryset(queryset=self.queryset.filter(order__user=self.request.user))
If you're supporting writable nested representations you'll need to write .create() or .update() methods that handle saving multiple objects.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
Probably there is the answer to this question, but I couldn't find it.
How to prevent User - from django.contrib.auth.models import User, creation if userProfileSerializer creating is unsuccessful. I saw a database transaction is an option but it says
While the simplicity of this transaction model is appealing, it also
makes it inefficient when traffic increases. Opening a transaction for
every view has some overhead. The impact on performance depends on the
query patterns of your application and on how well your database
handles locking.
#api_view(['POST'])
#permission_classes([AllowAny])
def register(request):
'''
Registers user to the server. Input should be in the format:
{"username": "username", "password": "1234abcd"}
'''
# Put the data from the request into the serializer
serializer = CreateUserSerializer(data=request.data)
# Validate the data
if serializer.is_valid():
# If it is valid, save the data (creates a user).
serializer.save()
userProfileSerializer = UserProfileSerializer(data=request.data)
userProfileSerializer.context['user_id'] = serializer.data['id']
userProfileSerializer.is_valid(raise_exception=True)
userProfileSerializer.save()
Serializer classes
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'first_name', 'last_name',)
extra_kwargs = {
'password': {'write_only': True}
}
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('title', 'organization', 'user_id')
def create(self, validated_data):
user_id = self.context["user_id"]
user_profile = UserProfile(**validated_data, user_id=user_id)
user_profile.save()
return user_profile
You can do the following:
check that UserProfileSerializer is valid, as you doing already: userProfileSerializer.is_valid(raise_exception=True)
save user serializer as usual after UserProfileSerializer is validated: serializer.save()
Their are some ways,
use transaction.atomic, which you know.
delete the created user after userProfileSerializer unsuccessfull.
Not a way but a trick, Validate both serializers first, confirming everything is all right, then save the user, add user id in profile_serializer, then save the profile_serializer at last. This avoids the transaction.
But it is just a trick; Use transaction as your use case requires it. Its the best way.
I have a form on my frontend where the user can update some info - part of the fields are supposed to update the default Django User Model, while others should update the User Profile model, which has a One-To-One Relationship to the User.
I am running the code below but get the following error:
AttributeError: 'str' object has no attribute '_meta'
api.py
# Get and Update User API
class UserAPI(generics.RetrieveUpdateAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = UserSerializer
def retrieve(self, request, *args, **kwargs):
serializer = self.serializer_class(request.user)
return Response(serializer.data)
def update(self, request, *args, **kwargs):
user_data = request.data['user']
profile_data = request.data['profile']
user_serializer = self.serializer_class(request.user, data=user_data, partial=True)
user_serializer.is_valid(raise_exception=True)
user_serializer.save()
if profile_data:
profile_serializer_class = ProfileSerializer
profile_serializer = ProfileSerializer(request.user.username, data=profile_data, partial=True)
print(profile_serializer)
profile_serializer.is_valid(raise_exception=True)
profile_serializer.save()
response = {'user': user_serializer.data, 'profile': profile_serializer.data}
return Response(response)
# UserProfile API
class ProfileAPI(generics.RetrieveAPIView):
lookup_field = 'user'
serializer_class = ProfileSerializer
queryset = Profile.objects.all()
serializers.py
# User Serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'email', 'first_name', 'last_name')
# Profile Serializer
class ProfileSerializer(serializers.ModelSerializer):
user = serializers.SlugRelatedField(read_only=True, slug_field="username")
class Meta:
model = Profile
fields = '__all__'
I am making a patch request in the following format:
{
"user": {
"username": "testuser"
},
"profile": {
"bio": "hello this is my bio"
}
}
The patch worked when I was only updating the User model, however I am not sure if this is the correct way to include the Profile model. What would I need to change to make this work?
The first positional argument to Serializer and its subclasses should be the instance you want to serialize, create, or update. In the case of ModelSerializer it is a model instance.
You should be therefore passing the Profile instance. I assume it is user.profile
profile_serializer = ProfileSerializer(request.user.profile, data=profile_data, partial=True)
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
I'm working on a project using django-rest-framework. In my API view, an authenticated user can create other users. But, only five. Then if there are five users registered by one user, I want to send him in the response that hit the limit. Then, I need to get on my serializer the authenticated user but, I can't find a way to pass it from my ModelViewSet to my serializer.
This is my code:
View:
class ChildUserViewSet(viewsets.ModelViewSet):
serializer_class = ChildUserSerializer
queryset = User.objects.all()
authentication_classes = (
TokenAuthentication,
)
permission_classes = (
IsAuthenticated,
)
def perform_create(self, serializer):
account_group = self.request.user.userprofile.get_account_group
mobile_number = serializer.data.get('mobile_number')
password = serializer.data.get('password')
user = serializer.save()
user.set_password(password)
user.save()
# Generate user profile
UserProfile.objects.create(
user=user,
mobile_number=mobile_number,
user_type=CHILD,
related_account_group=account_group,
)
Serializer:
class ChildUserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField()
class Meta:
model = User
fields = (
'first_name',
'last_name',
'email',
'password',
'mobile_number',
)
def validate(self, data):
"""
Check that the start is before the stop.
"""
# Get authenticated user for raise hit limit validation
def validate_email(self, value):
if User.objects.filter(email=value):
raise serializers.ValidationError("This field must be unique.")
return value
def create(self, validated_data):
username = generate_unique_username(
u'{0}{1}'.format(
validated_data['first_name'],
validated_data['last_name'],
)
)
user = User(
username=username,
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user.save()
return user
Then, in the def validate(self, data) function of my serializer, I want to get the currently authenticated user.
How can I pass the request.user from my APIView to my serializer?
I found an even easier way of accomplishing this! It turns out that Rest Framework's GenericAPIView base class (from which all of Rest Framework's generic View classes descend) includes a function called get_serializer_context():
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request,
'format': self.format_kwarg,
'view': self
}
As you can see, the returned context object contains the same request object that the View receives. This object then gets set when the serializer is initialized:
def get_serializer(self, *args, **kwargs):
"""
Return the serializer instance that should be used for validating and
deserializing input, and for serializing output.
"""
serializer_class = self.get_serializer_class()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
Thus to access the user who made the request, you just need to call self.context['request'].user from within your Serializer's validate_ function:
class TemplateSerializer(serializers.ModelSerializer):
def validate_parent(self, value):
print(self.context['request'].user)
return value
class Meta:
model = Template
And the best part is that you don't have to override anything in your ModelViewSet, they can stay as simple as you want them to:
class TemplateViewSet(viewsets.ModelViewSet):
serializer_class = TemplateSerializer
permission_classes = [IsAdmin]
In your views when you initialize serializer like
serializer = ChildUserSerializer(data=request.DATA,context={'request':request})
,send a context which contains request.Then in Serializers inside function call
request=self.context['request']
Then you can access request.user.
You can pass additional context to your serializer with serializer = ChildUserSerializer(data, context={'request': request}). You can then access the authenticated user via request.user within your serializer validation method.
In djangorestframework > 3.2.4 the rest_framework.generic.GenericAPIView class includes the http request by default in the serializer context.
So inside your serializer you can access it by: self.context['request'] and the user self.context['request'].user
So your ChildUserSerializer will look like:
class ChildUserSerializer(serializers.ModelSerializer):
mobile_number = serializers.CharField()
....
def validate(self, data):
"""
Check that the start is before the stop.
"""
# Get authenticated user for raise hit limit validation
user = self.context['request'].user
# do something with the user here
def validate_email(self, value):
if User.objects.filter(email=value):
raise serializers.ValidationError("This field must be unique.")
return value
...