Serializer to create user and profile in Django Rest Framework - django-rest-framework

I'm trying to serializer to create a user and your profile at the same time.
My code:
serializers
class UserProfileSerializer(
serializers.ModelSerializer
):
profile = ProfileSerializer(required=True)
def create(self, validated_data):
user = super(UserProfileSerializer, self).create(validated_data)
user.save()
profile_data = validated_data.pop('profile')
return user
class Meta:
model = User
exclude = ("groups", "user_permissions", )
class UserSerializer(
serializers.ModelSerializer
):
class Meta:
model = User
exclude = ("groups", "user_permissions", )
view
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
model = User
def get_serializer_class(self):
if self.request.method == "POST":
return UserProfileSerializer
return UserProfileSerializer
My problem is the user... When I trying to create a user/profile I get this error:
{
"profile": {
"user": [
"This field must be unique."
]
}
}

Related

Saving Single Post request to multiple database models

I have multiple users and I designed two models for two users and the rest is super user.
And I have the model User that holds common data for all users.
I want to save the patient post request to two different models: User and Patient; I tried these codes but, didn't work.
This is my Signup view
class PatientSignupView(generics.GenericAPIView):
serializer_class=PatientSignupSerializer
permission_classes = [
permissions.AllowAny
]
def post(self, request, *args, **kwargs):
user_serializer=self.get_serializer(data=request.data)
user_serializer.is_valid(raise_exception=True)
user=user_serializer.save()
return Response({
"user":UserSerializer(user, context=self.get_serializer_context()).data,
"patient":PatientSerializer(patient, context=self.get_serializer_context()).data,
"token":Token.objects.get(user=user).key,
"message":"account created successfully",
})
And here is my Signup Serializer class
class PatientSignupSerializer(serializers.ModelSerializer):
password_confirm = serializers.CharField(style={"input_type":"password"}, write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'is_doctor', 'is_patient', 'password',
'password_confirm']
extra_fields = {
'password': {'write_only': True},
}
def save(self, **kwargs):
user=User(
username=self.validated_data['username'],
email=self.validated_data['email']
)
password=self.validated_data['password']
password_confirm=self.validated_data['password_confirm']
if password !=password_confirm:
raise serializers.ValidationError({"error":"password do not match"})
user.set_password(password)
user.is_patient=True
user.is_doctor=False
user.save()
PatientSerializer.serialized_data(user)
return user
Here is the PatientSerializer and UserSerializer class
from rest_framework import serializers
from .models import Patient, User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username','email','is_doctor','is_patient']
class PatientSerializer(serializers.ModelSerializer):
class Meta:
model = Patient
fields = ['first_name', 'middle_name', 'last_name', 'address', 'phone_number', 'birth_date', 'gender']
def serialized_data(self, user, **kwargs):
patient = Patient.objects.create(
user = user,
first_name=self.validated_data['first_name'],
middle_name = self.validated_data['middle_name'],
last_name = self.validated_data['last_name'],
address = self.validated_data['address'],
phone_number = self.validated_data['phone_number'],
birth_date = self.validated_data['birthh_date'],
gender = self.validated_data['gender']
)
return patient
Thank you!
It is solved like this
The signup view is:
class PatientSignupView(generics.GenericAPIView):
serializer_class=PatientSignupSerializer
def post(self, request, *args, **kwargs):
user_serializer=self.get_serializer(data=request.data)
user_serializer.is_valid(raise_exception=True)
user=user_serializer.save()
return Response({
"user":UserSerializer(user, context=self.get_serializer_context()).data,
"token":Token.objects.get(user=user).key,
"message":"account created successfully",
})
The serializer class will looks like:
class PatientSignupSerializer(serializers.ModelSerializer):
password_confirm = serializers.CharField(style={"input_type":"password"}, write_only=True)
first_name = serializers.CharField()
middle_name = serializers.CharField()
last_name = serializers.CharField()
address = serializers.CharField()
phone_number= serializers.CharField()
birth_date = serializers.DateTimeField()
gender = serializers.CharField()
class Meta:
model = User
fields = '__all__'
extra_fields = {'password': {'write_only': True},}
def save(self, **kwargs):
user=User(
username=self.validated_data['username'],
email=self.validated_data['email']
)
password=self.validated_data['password']
password_confirm=self.validated_data['password_confirm']
if password !=password_confirm:
raise serializers.ValidationError({"error":"password do not match"})
user.set_password(password)
user.is_patient=True
user.is_doctor=False
user.save()
Patient.objects.create(user=user,
first_name=self.validated_data['first_name'],
middle_name=self.validated_data['middle_name'],
last_name=self.validated_data['last_name'],
address=self.validated_data['address'],
phone_number=self.validated_data['phone_number'],
birth_date=self.validated_data['birth_date'],
gender=self.validated_data['gender'])
return user

Django Rest Framework Relationships Serialization

I am trying to get serialized data for the below model with foreign keys but in the output i am only getting the ids instead of the fields of the model associated with hyperlink.
I have tried getting all the fields of the Skills model using the commented lines in the profile serializer but no luck
models.py
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
first_name = models.CharField(max_length=20, null=True)
last_name = models.CharField(max_length=20, null=True)
def __str__(self):
return 'Profile for user {}'.format(self.user.username)
class Skill(models.Model):
skill = models.CharField(max_length=20, null=True)
level = models.CharField(max_length=20, null=True)
user = models.ForeignKey(Profile, null=True,
on_delete=models.PROTECT, related_name='skills')
def __str__(self):
return '%s: %s: %s' % (self.user, self.skill, self.level)
serializer.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
# skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
# skills = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='skill-detail')
# skills = serializers.StringRelatedField(many=True)
# skills = serializers.HyperlinkedIdentityField( view_name = "skill-list", lookup_field = "user")
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
# user = serializers.ReadOnlyField(source='user.username')
# pdb.set_trace()
model = Profile
fields = ['id', 'user', 'url', 'skills']
views.py
class ProfileList(generics.ListCreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class ProfileDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
class SkillList(generics.ListCreateAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
class SkillDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Skill.objects.all()
serializer_class = SkillSerializer
urls.py
path('profiles/', views.ProfileList.as_view(), name='profile-list'),
path('profiles/<int:pk>/', views.ProfileDetail.as_view(), name='profile-detail'),
path('skills/', views.SkillList.as_view(), name='profile-list'),
path('skills/<int:pk>/', views.SkillDetail.as_view(), name='skill-list'),
Output: http://127.0.0.1:8000/authenticator/profiles/
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
57,
55
],
]
Expected output:
[ "id": 6,
"user": 4,
"url": "http://127.0.0.1:8000/authenticator/profiles/6/",
"skills": [
{
"user": 6,
"skill": "ABC",
"level": "Beginner"
},
{
"user": 6,
"skill": "DEF",
"level": "Intermediate"
},
]
]
This should normally work with the following in serializer.py:
class ProfileSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True) # serializes child model's data inside this model's data
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ['id', 'user', 'url', 'skills']
So basically uncomment the line in which the ProfileSerializer is told to serialize the skills with your SkillsSerializers, such that the entire skill objects are included in the result instead of its IDs.
You can use the concept of nested serializer for the solution.
Create two serializer ProfileReadSerializer and ProfileWriteSerializer
serializers.py
class SkillSerializer(serializers.ModelSerializer):
class Meta:
model = Skill
fields = ('user', 'skill', 'level')
class ProfileReadSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True, read_only=True)
url = HyperlinkedIdentityField( view_name="profile-detail", lookup_field = "id")
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
class ProfileWriteSerializer(serializers.ModelSerializer):
skills = SkillSerializer(many=True)
class Meta:
model = Profile
fields = ('id', 'user', 'url', 'skills')
In views you simply can use ModelViewSet to make things easy and use get_serializer_class
views.py
from rest_framework.viewsets import ModelViewSet
class ProfileVewSet(ModelViewSet):
queryset = Profile.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST' or self.request.method == 'PUT' or self.request.method == 'PATCH':
return ProfileWriteSerializer
else:
return ProfileReadSerializer
And at last if you are using ModelViewSet you need to change the urls.py
urls.py
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('profile', views.ProfileViewSet, base_name='profile')
urlpatterns = [
path('', include(router.urls))
]
I think this will solve your problem. Please have a look.

TypeError: 'FieldFile' object is not callable

I'm getting the TypeError: 'FieldFile' object is not callable when trying to get URL of uploaded file:
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
queryset = obj.config_id.config_file() ###
return ShoppingListSerializer(queryset, many=True).data
The model consists of the following fields:
class ShoppingList(models.Model):
id = models.CharField(max_length=40, primary_key=True)
name = models.CharField(max_length=40)
session_id = models.CharField(max_length=40)
config_file = models.FileField(upload_to=upload_config_file)
def __str__(self):
return self.id
What should be done with the queryset to eventually display uploaded file URL?
Can you edit your get_file_fields like this ?
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
queryset = ShoppingList.objects.filter(config_file = obj)
return [ShoppingListSerializer(cf).data for cf in queryset]
As written, you are trying to execute a field in this line obj.config_id.config_file().
If you want to serialize the object you can simply serialize the one model, you don't need a queryset.
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
return ShoppingListSerializer(obj).data

How to verify user url matches user in token in Django-Rest-Framework

I'm trying to create an object (called Post) in DRF where one of the fields is user, which should represent the user that created the object. I'm using Token-authentication, and I want to make sure that the request.user matches the user passed to the object. However, I'm using HyperLinkedModelSerializers so I pass the url of the user to the PostSerializer.
How do I get the url of the request.user? Do I have to create a url field in my user?
Here's the serializer classes:
class UserSerializer(serializers.HyperlinkedModelSerializer):
User = get_user_model()
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ('username', 'password')
def create(self, validated_data):
user = User.objects.create_user(**validated_data)
return user
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Post
fields = ('id', 'user', 'caption', 'video_link')
def create(self, validated_data):
return Post(**validated_data)
and here's the viewset
class PostViewSet(viewsets.ModelViewSet):
serializer_class = PostSerializer
queryset = Post.objects.all()
permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
If the user logged in system, you can rewrite PostSerializer like following:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('caption', 'video_link')
def create(self, validated_data):
post = Post(user=self.request.user, caption=validated_data['caption'], video_link=validated_data['video_link'])
post.save()
return validated_data

django-rest-framework CRUD many models in same view

I would like to be able to CRUD many models in the same API View.
In this case there are User and UserProfile Models in the same API View.
My Serializers
class UserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ('expected_calories',)
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Then my views
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserProfileViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
GET Api Call to
/api/v1/user/2/
{
"url": "http://localhost:8000/api/v1/user/2/",
"username": "jsalchichon",
"email": "jsalchichon#example.com",
"first_name": "John",
"last_name": "Salchichon",
"expected_calories": 0
}
But when I try to update the expected_calories field to 2500, The field is not updated.
Is this the right way to do this? What else should I do to be able to update the expected_calories of the UserProfile Model?
Thanks to you in advance.
I just did this:
class UserSerializer(serializers.HyperlinkedModelSerializer):
expected_calories = serializers.Field(source='userprofile.expected_calories')
class Meta:
model = User
fields = ('url', 'username', 'email', 'first_name', 'last_name', 'expected_calories')
def validate_expected_calories(self, attrs, source):
expected_calories = self.data.get('expected_calories')
if not isinstance(expected_calories, int):
raise serializers.ValidationError("expected_calories must be an integer")
attrs['expected_calories'] = expected_calories or self.object.userprofile.expected_calories
return attrs
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
def post_save(self, obj, created=False):
super(UserViewSet, self).post_save(obj, created)
expected_calories = self.request.DATA.get('expected_calories')
if isinstance(expected_calories, int):
obj.userprofile.expected_calories = self.request.DATA.get('expected_calories',
obj.userprofile.expected_calories)
obj.userprofile.save()
Not sure if it's the right way to handle this but it works.

Resources