In my project I want to set password as both read_only(because I have a separate endpoint to reset password) and write_only(because I don't want password send in the response).
Here is my serializer:
class UserSerializer(serializers.ModelSerializer):
"""A Serizlier class for User """
class Meta:
model = models.User
fields = ('id', 'email', 'phone_number', 'user_type', 'password')
extra_kwargs = { 'password': { 'write_only': True} }
read_only_fields = ('password',)
But I get an error saying:
AssertionError at /api/user/21/
May not set both read_only and
write_only
How can I have a field be both read_only and write_only?
Override the __init__() method of the serializer as,
class UserSerializer(serializers.ModelSerializer):
"""A Serizlier class for User """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['some_flag']:
self.fields['password'].read_only = True
else:
self.fields['password'].write_only = True
class Meta:
model = models.User
fields = ('id', 'email', 'phone_number', 'user_type', 'password')
# extra_kwargs = { 'password': { 'write_only': True} } # remove this
# read_only_fields = ('password',) # remove this
The some_flag variable is something that you should pass to the serializer either from the password reset view or from the other view
So extending on the answer of #JPG
The Serializer
class ProfileSerializer(serializers.ModelSerializer):
name = serializers.CharField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'create_method' in self.context:
self.fields['name'].write_only = True
else:
self.fields['name'].read_only = True
And the view set
class ProfileViewSet(viewsets.ModelViewSet):
serializer_class = ProfileSerializer
def get_serializer_context(self):
# Call Parent
context = super().get_serializer_context()
# Check If This Is a POST
if self.request.method == 'POST':
context['create_method'] = True
# Handoff
return context
This will allow name to be written on POST and read only on everything else
well - normally if you have two endpoints using a similar serializer which needs to differ only with a certain field/functionality you would create a base class and abstract it and only change/modify the parts of it that need to change. Here is what I would do.
class (serializers.ModelSerializer):
"""A Serizlier class for User """
class Meta:
model = models.User
fields = ('id', 'email', 'phone_number', 'user_type', 'password')
extra_kwargs = { 'password': { 'read_only': True} }
class UserSerializerForOtherView(UserSerializer):
class Meta(UserSerializer.Meta):
extra_kwargs = { 'password': { 'write_only': True} }
Now UserSerializerForOtherView inherits the same behaviour as UserSerializer and you now also have a new serializer should you want to further expand on functionality on this serializer alone.
All you will need to do is tell the other view to use the other serializer.
Related
I am trying to create a user, I need to override the create method, but after that, imagefield gets null value instead of given file
here is my code
views.py
class VerifyEmail(CreateAPIView):
serializer_class = UserSerializer
queryset = User.objects.none()
def create(self, request, *args, **kwargs):
print(request.POST.get("profile_picture"))
token = request.GET.get("token")
invite_info = Invitation.objects.get(new_token=token)
data = {
'email': invite_info.receiver,
'organization': invite_info.organization.pk,
'is_staff': request.GET.get('is_staff', False),
'is_superuser': request.GET.get('is_superuser', False),
'first_name': request.POST.get('first_name', ''),
'last_name': request.POST.get('last_name', ''),
'profile_picture': request.POST.get('profile_picture'),
'country': request.POST.get('country'),
'password': request.POST.get('password')
}
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
serializer.save()
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=201, headers=headers)
serilizers.py
# class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(
write_only=True,
required=True,
style={'input_type': 'password', 'placeholder': 'Password'}
)
class Meta:
model = User
fields = ('id', 'email', 'organization', 'first_name', 'last_name',
'country', 'profile_picture', 'date_joined', 'modification_date', "is_active",'password')
def create(self, validated_data):
validated_data['password'] = make_password(validated_data.get('password'))
return super(UserSerializer, self).create(validated_data)
imagefield in models.py
profile_picture = models.ImageField(upload_to="images",)
It's work fine when I use default implementation of create method,but when I try to override it dictionary data gets all values except "profile_picture" which gets None. please help me.
You should be able to get the file from request.FILES, not request.POST.
But, rather than overriding the View's create function like that, why not override the serializer's create function like this:
class ABCSerializer():
password = serializers.CharField(
write_only=True,
required=True,
style={'input_type': 'password', 'placeholder': 'Password'}
)
class Meta:
model = User
fields = ('id', 'email', 'organization', 'first_name', 'last_name',
'country', 'profile_picture', 'date_joined', 'modification_date', "is_active",'password')
def create(self, validated_data):
validated_data['password'] = make_password(validated_data.get('password'))
request = self.context['request']
token = request.GET.get("token")
invite_info = Invitation.objects.get(new_token=token)
validated_data['email'] = invite_info.receiver
validated_data['organization'] = invite_info.organization.pk return super(UserSerializer, self).create(validated_data)
In this way, you can remove the override of create function from view.
Finally, (optional) rather than using request.GET, why not pass the token information through URL argument ie path('/something/<token:str>', YourView.as_view()) and access that value through self.kwargs['token']
I trying to insert and update a writable nested serializer with Django Rest Framework, following examples like this. But it doesn't work, because somehow after I execute serializer.is_valid() it lose the reference from serializer.validated_data like if it never was sent.
What could I having doing wrong?
My models
class User(AbstractUser):
institution = models.ForeignKey(Institution, on_delete=None, null=True, blank=True)
class Meta:
db_table = 'User'
managed = True
verbose_name = 'Users'
verbose_name_plural = 'Users'
ordering = ['id']
def __str__(self):
return self.email
class Institution(models.Model):
id = models.AutoField(db_column='id', primary_key=True)
name = models.CharField(db_column='name', max_length=255, null=False)
country = models.CharField(db_column='country', max_length=255, null=False)
class Meta:
db_table = 'Institution'
managed = True
verbose_name = 'Institutions'
verbose_name_plural = 'Institutions'
ordering = ['id']
def __str__(self):
return self.name
My serializers
class InstitutionSerializer(serializers.ModelSerializer):
class Meta:
model = Institution
fields = '__all__'
datatables_always_serialize = ('id', 'name', 'country')
class UserSerializer(serializers.HyperlinkedModelSerializer):
institution = InstitutionSerializer()
def create(self, validated_data):
return User.objects.create_user(**validated_data)
def update(self, instance, validated_data):
institution_data = validated_data['institution']
instance.institution = Institution.objects.get(pk=institution_data['id'])
return instance
class Meta:
model = User
fields = (
'id',
'username',
'first_name',
'last_name',
'email',
'password',
'is_active',
'institution',
)
datatables_always_serialize = (
'id',
'username',
'first_name',
'last_name',
'email',
'is_active',
'institution',
)
My view
class UserViewSet(ModelViewSet):
serializer_class = UserSerializer
permission_classes = (IsSuperUserPermission,)
def list(self, request, **kwargs):
params = Q()
if 'search[value]' in request.GET and request.GET['search[value]'] != '':
params = Q(username__icontains=request.GET['search[value]']) |\
Q(first_name__icontains=request.GET['search[value]']) |\
Q(last_name__icontains=request.GET['search[value]']) |\
Q(email__icontains=request.GET['search[value]']) |\
Q(institution__name__icontains=request.GET['search[value]'])
queryset = User.objects.filter(params).select_related().order_by('id')
serializer = self.serializer_class(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, *args, **kwargs):
queryset = User.objects.filter(pk=request.GET['pk']).select_related()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
def get_permissions(self):
if self.action in ('create',):
self.permission_classes = [AllowAny, ]
return super(self.__class__, self).get_permissions()
def create(self, request, *args, **kwargs):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
serializer.create(serializer.validated_data)
return Response(serializer.data)
else:
return Response(serializer.errors, status.HTTP_500_INTERNAL_SERVER_ERROR)
def partial_update(self, request, *args, **kwargs):
user = User.objects.get(pk=request.data['id'])
serializer = UserSerializer(instance=user, data=request.data, partial=True)
if serializer.is_valid():
if 'password' in serializer.validated_data:
serializer.validated_data['password'] = make_password(serializer.validated_data['password'])
serializer.update(user, serializer.validated_data)
return Response(serializer.data)
else:
return Response(serializer.errors, status.HTTP_500_INTERNAL_SERVER_ERROR)
Edit.
I submitting data like this:
{
"username": "BLA",
"email": "BLA#BLA.com",
"first_name": "BLA",
"last_name": "BLA",
"institution": 1,
"is_active": true,
"password": "bla12345"
}
Why this problem?
In your update payload, you are providing the institution data as an integer which represents the PK. But you've also defined a nested serializer InstitutionSerializer() inside UserSerializer() class. So, DRF expects a dict like object (DRF probably raise some error by mentioning so. I'm not sure why it's not happened in this situation).
What is the solution?
Since you are passing the institution id, I assume, you need the nested output only on HTTP GET requests. So, override the __init__() method of the UserSerializer() class and restrict the nested serializer usage to only HTTP GET requests
Here is the code
class UserSerializer(serializers.HyperlinkedModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['institution'] = InstitutionSerializer()
institution = InstitutionSerializer() # remove this
def create(self, validated_data):
return User.objects.create_user(**validated_data)
def update(self, instance, validated_data):
institution_data = validated_data['institution']
instance.institution = Institution.objects.get(pk=institution_data['id'])
return instance
class Meta:
model = User
fields = (
'id',
'username',
'first_name',
'last_name',
'email',
'password',
'is_active',
'institution',
)
datatables_always_serialize = (
'id',
'username',
'first_name',
'last_name',
'email',
'is_active',
'institution',
)
UPDATE-1
Change your partial_update() method of UserViewSet class as below,
def partial_update(self, request, *args, **kwargs):
user = User.objects.get(pk=request.data['id'])
serializer = UserSerializer(instance=user, data=request.data, partial=True, context={"request": request}) # change is here <<<
if serializer.is_valid():
if 'password' in serializer.validated_data:
serializer.validated_data['password'] = make_password(serializer.validated_data['password'])
serializer.update(user, serializer.validated_data)
return Response(serializer.data)
else:
return Response(serializer.errors, status.HTTP_500_INTERNAL_SERVER_ERROR)
I found a way to solve my problem, using JPG idea. I just add an else using a PrimaryKeyRelatedField to allow serializer to get reference from the model from id.
There might be another solution, bu this one works and looks better than multiple serializers.
class UserSerializer(serializers.HyperlinkedModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['institution'] = InstitutionSerializer()
else:
self.fields['institution'] = serializers.PrimaryKeyRelatedField(queryset=Institution.objects.all())
When I try to add a User with a list of groups I get an error stating that "A group with this name already exists". Here are my params:
{
'email': 'test#test.com',
'first_name': 'Bob',
'last_name': 'Jones',
'groups': [{'url': 'http://localhost:8000/api/groups/1/', 'name': 'Admin'}]
}
serializers.py
class GroupSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializer to interact with the Groups model.
"""
class Meta:
model = Group
fields = ('url', 'name', 'id')
class UserSerializer(serializers.HyperlinkedModelSerializer):
"""
Serializer to interact with the Users model.
"""
url = serializers.HyperlinkedIdentityField(view_name='users-detail')
groups = GroupSerializer(many=True)
class Meta:
model = User
fields = ('id', 'url', 'username', 'email',
'groups', 'first_name', 'last_name', 'is_superuser', 'is_staff', 'is_active')
views.py
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = UserSerializer
queryset = User.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
remove nicely the validator for group name:
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ('name',)
extra_kwargs = {
'name': {'validators': []},
}
I want to send a custom response from serializers create view to front-end of my application. I tried rest framework Response tutorials but it does not work. My code is:
class UserSerializer(serializers.ModelSerializer):
"""Serializer to serialize user model object"""
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
"""create a new user"""
firstname = self.initial_data['first_name']
lastname = self.initial_data['last_name']
fullname = str(firstname) +" "+ str(lastname)
email = self.initial_data['username'].lower()
try:
customer = User.create(
name=fullname,
email=email)
except Error as e:
error = {'message': e._message or 'Unknown error'}
return Response(error,status=status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializer(serializers.ModelSerializer):
"""Serializer to serialize user model object"""
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name')
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
"""create a new user"""
firstname = validated_data['first_name']
lastname = validated_data['last_name']
fullname = str(firstname) +" "+ str(lastname)
email = validated_data['username'].lower()
try:
customer = User.objects.create(
name=fullname,
email=email)
return customer
except Exception as e:
error = {'message': ",".join(e.args) if len(e.args) > 0 else 'Unknown Error'}
raise serializers.ValidationError(error)
So you want to change the representation of the User (the shape of the JSON response) from this view. To do this you need to change the .to_representation(self, obj) method in the serializer:
class UserSerializer(serializers.ModelSerializer):
"""Serializer to serialize user model object"""
class Meta:
model = User
fields = ('id', 'username', 'password', 'first_name', 'last_name',)
write_only_fields = ('id', 'password',)
def to_representation(self, obj):
return {
'firstname': obj.first_name,
'lastname': obj.last_name,
'fullname': obj.first_name+' '+obj.last_name,
'email': obj.username.lower()
}
This should mean that whenever a request is made that uses this serializer the user will only ever see those 4 fields in the JSON, e.g.
{
"firstname":"Salman",
"lastname": "Ahmed",
"fullname": "Salman Ahmed",
"email": "salman_ahmed#email.com"
}
More details on this is hidden in their docs here.
thee are my serializers for different users
class PlayerSerializer(serializers.ModelSerializer):
class Meta:
model = player
fields = ('contact_name', 'contact_email', 'address',)
class ManagerSerializer(serializers.ModelSerializer):
class Meta:
model = Manager
fields = ('contact_name', 'contact_email', 'address',
'city', 'country',)
class CoachSerializer(serializers.ModelSerializer):
class Meta:
model = Shipper
fields = (' 'contact_name', 'contact_email', 'address', 'city', 'about', 'country',)
these are my user Serializers with relation with each user type
class playerUserSerialzers(serializers.ModelSerializer):
player =PlayerSerializer()
class Meta:
model = MyUser
fields = ('email', 'id', 'password', 'player',)
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
player_data = validated_data.pop('player')
user = MyUser(
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user = MyUser.objects.create(**validated_data)
Player.objects.create(user=user, **player_data)
user.save()
return user
class managerUserSerialzers(serializers.ModelSerializer):
manager =ManagerSerializer()
class Meta:
model = MyUser
fields = ('email', 'id', 'password', 'manager',)
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
manager_data = validated_data.pop('manager')
user = MyUser(
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user = MyUser.objects.create(**validated_data)
Manager.objects.create(user=user, **manager_data)
user.save()
return user
class managerUserSerialzers(serializers.ModelSerializer):
coach =CoachSerializer()
class Meta:
model = MyUser
fields = ('email', 'id', 'password', 'coach',)
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
coach_data = validated_data.pop('coach')
user = MyUser(
email=validated_data['email'],
)
user.set_password(validated_data['password'])
user = MyUser.objects.create(**validated_data)
Coach.objects.create(user=user, **coach_data)
user.save()
return user
this doesn't work but i would like to use different serializers based on the front end request
class MyUserViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
queryset = MyUser.objects.all()
pagination_class = LimitOffsetPagination
permission_classes = (AllowAny,)
how do i switch bases on serializer class here
def get_serializer_class(self,):
if self.request.query_params.get("player", None):
return MyBrokerUserSerialzers
From the documentation you can specify the serializer_class property telling the right serializer you want.
Then, as you are inheriting many mixing classes you can overwrite their methods and get the information from your frontend. For each kind of request method you will have to get the frontend information in a different way.
class MyUserViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
queryset = MyUser.objects.all()
pagination_class = LimitOffsetPagination
permission_classes = (AllowAny,)
def define_serializer_class(self, serializer_class_name):
serializer_class = serializer_class_name
def get(self, request, *args, **kwargs):
#here in GET METHOD you wil user the RetrieveModelMixin
#So get the kind of serializer from you get params in request.GET["param name"]
#and set the right one with a 'if' condition and calling
# self.define_serializer_class(<the name of the right serializer class>)
return self.retrieve(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
#here in POST METHOD you wil user the CreateModelMixin
#So get the kind of serializer from you post playload in request.POST["param name"]
#and set the right one with a 'if' condition and calling
# self.define_serializer_class(<the name of the right serializer class>)
return self.update(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
#Do the same here get the info from the frontend
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
#Do the same here get the info from the frontend
return self.destroy(request, *args, **kwargs)
I'm sorry I cannot tell the right code to do what you want, but
the comments in the code explain step-by-step a way to solve your problem. I never have done it myself because I don't like to use mixings (they make up everying), but it will work for you. Just find the right way to get the information from the frontend for each kind of HTTP method that the mixins use and set the serializer_class as I said above.
I hope it helps you.
In my opinion this is unclear what you are trying to achieve and why are you doing things this way.
Let me put it in the perspective, if you have 3 seaparate objects, create 3 separate backends for them and you do not need anything else.
class UserMixin(object):
def create(self, validated_data):
user = MyUser(email=validated_data.pop('email'))
user.set_password(validated_data.pop('password')
user.save() # now you can use user in relations
validated_data['user'] = user
super(UserMixin, self).create(**validated_data)
return user
class PlayerSerializer(serializers.ModelSerializer, UserMixin):
class Meta:
model = Player
fields = ('id', 'email', 'password', 'contact_name', 'contact_email',
'address')
read_only_fields = ('id',)
extra_kwargs = {'password': {'write_only': True}}
class ManagerSerializer(serializers.ModelSerializer, UserMixin):
class Meta:
model = Manager
fields = ('id', 'email', 'password', 'contact_name', 'contact_email',
'address', 'city', 'country')
read_only_fields = ('id',)
extra_kwargs = {'password': {'write_only': True}}
class CoachSerializer(serializers.ModelSerializer, UserMixin):
class Meta:
model = Shipper
fields = ('id', 'email', 'password', 'contact_name', 'contact_email',
'address', 'city', 'about', 'country',)
read_only_fields = ('id',)
extra_kwargs = {'password': {'write_only': True}}
# now 3 viewsets
class PlayerViewset(viewsets.ModelViewSet):
model = Player
queryset = Player.objects.all()
serializer_class = PlayerSerializer
class ManagerViewset(viewsets.ModelViewSet):
model = Manager
queryset = Manager.objects.all()
serializer_class = ManagerSerializer
class CoachViewset(viewsets.ModelViewSet):
model = Shipper
queryset = Shipper.objects.all()
serializer_class = CoachSerializer
# register api ends
api_router.register('player', PlayerViewset)
api_router.register('manager', ManagerViewset)
api_router.register('coach', CoachViewset)
simple, efective and not much work...
p.s. class names starts with capital letter - please read pep-8