My Model is
class UserInfo(models.Model):
user = models.OneToOneField(User, unique=True)
mobile_no = models.CharField(max_length=10, blank=True)
and serialzer is :
class UserInfoSerializer(serializers.ModelSerializer):
class Meta:
model = UserInfo
fields = ('mobile_no','user')
but whenever I tried to use this
serializer = UserInfoSerializer(data=data)
if serializer.is_valid():
serializer.save()
It is not saving the data and giving errors.
Is there any method to use other then this to for using Primary key.
You should use PrimaryKeyRelatedField
add this to your serializer
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
Your UserInfoSerializer should look like:
class UserInfoSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = UserInfo
fields = ('mobile_no','user')
Update
If you want to update existing object in database then you have to pass model instance as an argument to UserInfoSerializer constructor.
user_info = self.get_object()
serializer = UserInfoSerializer(user_info, data=data)
Related
I have the following ModelSerializer with a create method. In this method I call the model's update_or_create method. But when I do this, the serializer's validation raises the error
rest_framework.exceptions.ValidationError: [{'non_field_errors': [ErrorDetail(string='The fields user_id, capacity_id must make a unique set.', code='unique')]}, {}].
I thought that since I'm using update_or_create, it would find the row that matches validated data's user_id and capacity_id, and then update that row. But the validation runs before create, and the data is not valid because of the unique constraint. So how do I ignore this constraint?
class ActivatedCapacitySerializer(serializers.ModelSerializer):
user_id = serializers.IntegerField(required=False)
capacity_id = serializers.IntegerField(required=False)
class Meta:
model = ActivatedCapacity
fields = ('user_id', 'capacity_id', 'active')
def create(self, validated_data):
activated_capacity = ActivatedCapacity.objects.update_or_create(
user_id=validated_data['user_id'],
capacity_id=validated_data['capacity_id'],
defaults = {
'active': validated_data['active']
}
)
return activated_capacity
Models.py
class ActivatedCapacity(models.Model):
user_id = models.IntegerField()
capacity_id = models.IntegerField()
active = models.BooleanField(default=False)
class Meta:
unique_together = ('user_id', 'capacity_id',)
I just had to include in the serializer's class Meta an empty validators list, so it will override the model's default validators.
class ActivatedCapacitySerializer(serializers.ModelSerializer):
user_id = serializers.IntegerField(required=False)
capacity_id = serializers.IntegerField(required=False)
class Meta:
model = ActivatedCapacity
fields = ('user_id', 'capacity_id', 'active')
validators = []
...
I'm working on extending the Basic User model to additional fields. I had created a Profile Model that should have a OneToOneRelation. I'm working with serializers. Now when I try to post a dummy user, I get this error:
**TypeError: User() got an unexpected keyword argument 'street'**
If I send only the user it works well. I know that the 'street' argument is not part of the User, but part of the Profile that should store later.
I tried to solve this with 'request.POST.pop' for every value and parsed to dict but then no data will be transmitted. Additionally, I had no success with Signals.
Does anyone have an idea how I can make this work, as the user and profile will be created at the same time? The user must save first and pass the id its generating to the Profile that is referenced to it.
Models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=CASCADE, null=True)
street = models.CharField(name="street", max_length=100)
number = models.CharField(name="number", max_length=10)
plz = models.CharField(name="plz", max_length=10)
city = models.CharField(name="city", max_length=100)
phone = models.CharField(name="phone", max_length=20)
locked = models.BooleanField(name="locked", default=False)
Serializer.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['street', 'number', 'plz', 'city', 'phone']
Views.py:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.create(validated_data=request.data)
profileSerializer = ProfileSerializer(instance=user ,data=request.data)
if profileSerializer.is_valid():
profileSerializer.create()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
You can rewrite the serializer to include profile. And then override the create method.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(**profile_data, user=user)
return user
Then your view becomes:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
The request to server should then be modified as:
{
profile: {
... // profile specific attributes like street, number, city
},
... // user specific attibutes
}
PS: To update the instance, you should override the update method of serializer.
I have a model with a one-to-one relationship with a main model:
class User(models.Model):
id = models.BigIntegerField(primary_key=True)
username = models.CharField(max_length=100, blank=True)
class AggregatedStats(models.Model):
user_id = models.ForeignKey('User', on_delete=models.DO_NOTHING, unique=True)
followers_30d = models.BigIntegerField(blank=True)
I have written the following serializers:
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'followers']
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = UserSerializer(source='user.username')
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
I am trying to return the username from the User model, but whatever I try to get it, the best I can do is get the hyperlinked related field from user, but not the actual "username" attribute. How would you return this?
You can simply create a field and return it:
class AggregatedStatsSerializer(serializers.HyperlinkedModelSerializer):
username = SerializerMethodField()
class Meta:
model = AggregatedStats
fields = ['followers_30d', 'username']
def get_username(self, obj):
return obj.user_id.username
My question is somewhat related to this one with some differences. I have a model similar to this one:
class Project(models.Model):
project_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
created_by_id = models.ForeignKey('auth.User', related_name='project', on_delete=models.SET_NULL, blank=True, null=True)
created_by = models.CharField(max_length=255, default="unknown")
created = models.DateTimeField(auto_now_add=True)
With the following serializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by_id.username')
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created')
And corresponding view:
class projectsView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(created_by_id=self.request.user)
This code behaves like I want but forces information redundancy and does not leverage the underlying relationnal database. I tried to use the info from the linked question to achieve a "write user id on database but return username on "get"" in a flat json without success:
Removing the "created_by" field in the model. Replacing the serializer with:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Which would NOT 100% give me what I want, i.e. replace the user id with the username in a flat json but return something like: {'project_id': <uuid>, 'created_by': <user json object>, 'created': <data>}. But still I get a {'created_by_id': ['This field is required.']} 400 error.
Question: How can I write a user id to a database object from the request.user information to refer to an actual user id but return a simple username in the GET request on the projectsView endpoint without explicitly storing the username in the Model? Or more generally speaking, how can I serialize database objects (Django models) into customer json response by using default serialization DRF features and default DRF views mixins?
Alternate formulation of the question: How can I store an ID reference to another DB record in my model (that can be accessed without it being supplied by the payload) but deserialize a derived information from that object reference at the serializer level such as one specific field of the referenced object?
I would recommend you to use Two different serializers for Get and POST operations. Change your serializers.py as
class ProjectGetSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by=validated_data['created_by_id'].username)
class Meta:
model = Project
fields = '__all__'
Also, I reccomend ModelViewSet for API class if you are looking for CRUD operations. Hence the view will be like this,
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return ProjectCreateSerializer
return ProjectGetSerializer
So, the payload to create Project is,
{
}
One thing you should remember, while you trying to create Project user must logged-in
UPDATE - 1
serializer.py
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by_id=self.context['request'].user)
views.py
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectCreateSerializer
The error is in the write_only field options. The required parameter default value is set to True while the intent is to not make it required if we take a look at the model. Here in the view, I use the perform_create as post processing to save on the Model DB representation. Since required default value is True at the creation level, the first .save() to the DB fails. Since this is purely internal logic, the required is not necessary. So simply adding the required=False option on the PrimaryKeyRelatedField does the job:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Enforcing the required=True at the Model level as well would require to override the .save function of the serializer if I insist on playing with the logic purely at the serializer level for deserialization. There might be a way to get the user ref within the serializer as well to keep the views implementation even more 'default'... This can be done by using the default value from Jerin:
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by',
write_only=True,
required=False,
default=serializers.CurrentUserDefault())
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Now to flaten the json with username only, you need to use a slug field instead of the UserSerializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.SlugRelatedField(
queryset=User.objects.all(), slug_field="username")
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
And then only the username field value of the User Model will show at the create_by json tag on the get payload.
UPDATE - 1
After some more tweaking here is the final version I came up with:
class ProjectSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), write_only=True, required=False, default=serializers.CurrentUserDefault())
created_by = serializers.SerializerMethodField('creator')
def creator(self, obj):
return obj.created_by_id.username
class Meta:
model = Project
fields = ('project_id', 'created_by_id', 'created_by', 'created')
I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
Saving is working perfectly. But now it is showing the following error when I call the GET method.
AttributeError at /api/v1/customer 'str' object has no attribute 'values'
Request Method: GET
bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
bcusomer/serializers.py
class CustomerDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = BCustomer
fields = ('city', 'phone')
class CustomerSerializer(serializers.ModelSerializer):
customer_details = CustomerDetailsSerializer()
class Meta:
model = get_user_model()
fields = ('id','first_name', 'email', 'customer_details')
def create(self, validated_data):
request = self.context.get('request')
customer_details_data = validated_data.pop('customer_details')
customer_user = get_user_model().objects.create(**validated_data)
BCustomer.objects.create(customer=customer_user, user=request.user, **customer_details_data)
customer_user.customer_details = customer_details_data
return customer_user
class CustomerListSerializer(serializers.ModelSerializer):
model = get_user_model()
fields = '__all__'
class Meta:
model = get_user_model()
fields = '__all__'
bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
def get_queryset(self):
queryset = BCustomer.objects.all()
return queryset
def get_serializer_class(self):
if self.action == 'list' or self.action == 'retrieve':
return CustomerListSerializer
return CustomerSerializer
bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
Data post parameter format
{
"first_name":"Myname",
"email":"testemail#gmail.com",
"customer_details": {
"city":"citys",
"phone":"04722874567",
}
}
You should remove model and fields from CustomListSerializer
class CustomerListSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = '__all__'
customer_details = CustomerDetailsSerializer()
You need to set the source argument to point to the user model's customer. Most probably:
customer_details = CustomerDetailsSerializer(source='customer')
(or maybe source='bcustomer', not sure if it reversed the field name or class name).
On a side not, you should not need the ListSerializer at all. The list method will call the serializer with the many=True argument on CustomerSerializer which will create the ListSerializer appropriately.