I have a non-model serializer which looks like this:
class NonModelSerializer(Serializer):
secret_number = IntegerField()
user = ???
def save(**kwargs):
... do something with the secret number and user ...
What shall be written instead of ??? so that my serializer accepts ID of a user and in save() method, I see the user of the given ID in the user field? Something like ModelChoiceField from plain Django.
you should use PrimaryKeyRelatedField,:
class NonModelSerializer(serializers.Serializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
secret_number = serializers.IntegerField()
class Meta:
fields = ('user', 'secret_number')
def create(self, validated_data):
print(self.validated_data)
pass
def update(self, instance, validated_data):
pass
I suggest you override create and update instead of save, but you can access the selected user in save by self.validated_data too.
Related
I have a model with two fields user_id and com_id but the issue is when I call serializer.is_valid() I am getting the empty data in the def validate(self, data). and I found out from the internet that the forign ids are always read only how to fix it because I have to validate that data and later I want to create the objects inside def create model.
models.py
class Member(models.Model ):
user = models.ForeignKey("Profile",verbose_name='User Phone', on_delete=models.CASCADE)
com = models.ForeignKey(Committee, on_delete=models.CASCADE,verbose_name='Committee Name')
serializer.py
class JoinCommitteeRequestSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['user_id', 'com_id']
def validate(self, data):
print('lll', data)
return data
I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret
I have this serializer:
class ChangePasswordSerializer(DynamicFieldsModelSerializer):
current = serializers.CharField()
confirm = serializers.CharField()
class Meta:
model = models.User
fields = ('password', 'current', 'confirm')
validators = []
def update(self, instance, validated_data):
if instance.check_password(validated_data.get('current')):
instance.set_password(validated_data['password'])
instance.save()
else:
raise serializers.ValidationError("Current password is not correct")
return instance
I have this inside my update function:
instance.check_password(validated_data.get('current'))
but I want this action happens outside of function update in validate_current function, but I dont know how
You can write custom validator for the current password
def validate_current(self, value):
if not self.instance.check_password(value):
raise serializers.ValidationError("Current password is not correct")
return value
you will have to pass instance from view in serializer
During registration of a user I would like to have both a User object and a EmailContact object created in one api call. The two objects should not be linked.
I have the following serializer:
class RegistrationSerializer(serializers.Serializer):
userserializer=UserAccountSerializer() #reuse existing modelserializer
emailcontactserializer=EmailContactSerializer() #reuse existing modelserializer
def create(self, validated_data):
emailcontact_data = validated_data.pop('emailcontactserializer')
user_data = validated_data.pop('userserializer')
emailcontact= EmailContact.objects.create(**emailcontact_data)
user= User.objects.create(**user_data)
return user
and the following Apiview:
class RegistrationAPIView(APIView):
permission_classes = (AllowAny,)
serializer_class = RegistrationSerializer
def post(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
The error I get is the following (occurs after the serializer.save()):
AttributeError at /api/register
Got AttributeError when attempting to get a value for field userserializer on serializer RegistrationSerializer.
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 'userserializer'.
In your RegistrationSerializer.create() method, you're returning a User object. The serializer will try to serialize that object into this representation:
{
'userserializer': x,
'emailcontactserializer': y
}
But it's complaining because the User you returned doesn't have a userserializer field.
If you really want to return a User from this API call, you could make your RegistrationSerializer a ModelSerializer with Meta.model=User, and override perform_create to pop out the emailcontact_data. (I'd name the field something like RegistrationSerializer.email_contact to make the representation clearer, IMO the phrase "serializer" shouldn't be present on the client-visible portion of the API).
Alternatively, if you want to render both of your sub-serializers, you can create a RegistrationSerializer instance in RegistrationSerializer.create by passing in the data, something like
return RegistrationSerializer(data={'emailcontactserializer':
emailcontact_data, 'userserializer': user_data})
In order to have a non-readonly PrimaryKeyRelatedField, you are required to provide a queryset that contains valid options.
How can I properly populate that queryset based on the current request (user)?
The key is to subclass PrimaryKeyRelatedField and overload the get_queryset method, using the user information from the request context:
class UserFilteredPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
request = self.context.get('request', None)
queryset = super(UserFilteredPrimaryKeyRelatedField, self).get_queryset()
if not request or not queryset:
return None
return queryset.filter(user=request.user)
You can then use this new serializer just like the (unfiltered) original:
class MySerializer(serializers.ModelSerializer):
related = UserFilteredPrimaryKeyRelatedField(queryset=MyModel.objects)
Whenever the serializer accesses the queryset, it will be filtered such that only objects owned by the current user are returned.
View has a
self.request.user
attribute which you can then use to fetch user related queryset
eg
queryset = Products.objects.get(customer=self.request.user)