I have an api that will do a patch on a resource (MyUser). It validates ok and seems to save the object, however when querying the database the changes have not been saved.
class UserSignupView(generics.UpdateAPIView):
serializer_class = MyUserSerializer
def get_object(self, email):
obj = MyUser.objects.get(email=email)
self.check_object_permissions(self.request, obj)
return obj
def patch(self, request):
print(request.user)
user = self.get_object(request.user.email)
print(user.street)
serializer = MyUserSerializer(user, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
savedUser = MyUser.objects.get(email=request.user.email)
print(savedUser.street)
print(serializer.data)
return Response(serializer.data)
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = MyUser
fields = (
'id', 'first_name', 'last_name', 'email', 'phone_number', 'street', 'locality', 'city',
'county', 'postcode')
Looking at the print statements I get:
user#example.com
None
123 Fake Street
MyUser object
It returns the correct serialised data which contains the changes but the database does not have the changes. The database connection is ok as I can query it and make other reads/writes/etc. It's pretty much the same as the UpdateModelMixin except I've had to override the get_object with a passed in parameter.
Try to override the update method and see what happens:
class MyUserSerializer(serializers.ModelSerializer):
class Meta:
model = MyUser
fields = (
'id', 'first_name', 'last_name', 'email', 'phone_number', 'street', 'locality', 'city',
'county', 'postcode')
def update(self, 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.email = validated_data.get('email',instance.email)
instance.phone_number = validated_data.get('phone_number',instance.phone_number)
instance.street = validated_data.get('street',instance.street)
instance.locality = validated_data.get('locality',instance.locality)
instance.city = validated_data.get('city',instance.city)
instance.county = validated_data.get('county',instance.county)
instance.postcode = validated_data.get('postcode',instance.postcode)
instance.save()
return instance
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'm using Django 2.0 and Django REST Framework to write REST API.
my settings.py contains settings for DRF
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
]
}
and every request must be signed using any of the DEFAULT_AUTHENTICATION_CLASSES method.
In contacts/serializers.py
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
instance = Contact.objects.create(**validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
print(validated_data) gives following data
{'first_name': 'Anshuman', 'last_name': 'Upadhyay', 'date_of_birth': datetime.date(2018, 5, 15), 'contactphonenumber_set': [], 'user_id': <SimpleLazyObject: <User: anuj>>}
The user_id is SimpleLazyObject thus giving error on save
TypeError: int() argument must be a string, a bytes-like object or a number, not 'SimpleLazyObject'
The error is on the line instance = Contact.objects.create(**validated_data)
How can I pass authenticated user to user field?
Edit 2: contacts/models.py
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
contacts/views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
You could use self.context for this http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context
If you are using GenericAPIView context is passed automatically
First of all you don't need to pass user_id to your serializer and then you need to update your ContactSerializer to look like this.
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
instance = Contact.objects.create(user=self.context['request'].user, **validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
You shouldn't overwrite perform_create
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
Assuming your Contact model contains an FK relation with AuthUserModel. So, your instance creation statement must be like this,
user = get some user instance from `validated` data
instance = Contact.objects.create(user=user,**validated_data)
This is the general solution for your question, If you add your Contact model, we could help you more
UPDATE-1
Change your create() as below,
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
user = validated_data.pop('user') # change 1
instance = Contact.objects.create(user=user, **validated_data) # # change 2
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
Update-2
You could use user = serializers.CurrentUserDefault() in serializer as below,
class ContactSerializer(serializers.HyperlinkedModelSerializer):
phone_numbers = ContactPhoneNumberSerializer(source='contactphonenumber_set', many=True)
user = serializers.CurrentUserDefault()
class Meta:
model = Contact
fields = ('url', 'first_name', 'last_name', 'full_name', 'date_of_birth', 'phone_numbers')
def create(self, validated_data):
phone_numbers = validated_data.pop('contactphonenumber_set')
emails = validated_data.pop('contactemail_set')
user = validated_data.pop('user_id')
instance = Contact.objects.create(user=user, **validated_data)
for phone_data in phone_numbers:
ContactPhoneNumber.objects.create(contact=instance, **phone_data)
return instance
CurrentUserDefault() is do the same job as self.context.get('request').user
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