Nested ManytoMany field not in the Response - python

I am trying to write a nested serializer which would add serialize 2 models in the same view. Serialization seems to work fine since changes get reflected in the database but I am not able to get the many-to-many related field data in the response. I have been trying to figure out what the issue might be but still no progress.
Here is my code:
Model
class User(AbstractBaseUser):
AccountName = models.ManyToManyField(Account,
through='User_Account',
through_fields=('user', 'acc'),
related_name='AccountData',
blank=True)
EmailId = models.EmailField(max_length=128, blank=False, null=False)
USERNAME_FIELD = 'EmailId'
REQUIRED_FIELDS = ['AccountName']
class Account(models.Model):
AccountName = models.TextField(max_length=100, blank=False, null=False)
Serializer
class AccountCreationSerializer(ModelSerializer):
class Meta:
model = Account
fields = ["AccountName"]
class SignUpSerializer1(ModelSerializer):
AccountData = AccountCreationSerializer(read_only=True, many=True)
class Meta:
model = User
fields = ['EmailId', 'AccountData', 'password']
extra_kwargs = {'password': {'write_only': True, 'required': True}}
def validate(self, attrs):
attrs = super(SignUpSerializer1, self).validate(attrs=attrs)
attrs.update({"AccountData": self.initial_data.get("AccountData")})
return attrs
def create(self, validated_data):
AccountName_data = validated_data.pop('AccountData')
acc = Account.objects.create(AccountName=AccountName_data)
userAcc = User.objects.create_user(**validated_data)
if acc:
userAcc.AccountName.add(acc)
print("added")
return userAcc
View
class SignUpView(APIView):
serializer_class1 = SignUpSerializer1
def post(self, request, *args, **kwargs):
if request.data['CreateAccount']:
serializer = self.serializer_class1(data=request.data)
is_valid_serializer = serializer.is_valid(raise_exception=True)
if is_valid_serializer:
with transaction.atomic():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
raise Exception("Bad error")
Request1
"EmailId" : "xyz#gmail.com",
"AccountData":{"AccountName":"TestAcc1"},
"CreateAccount": true,
"password" : "xyz"
Response
"EmailId": "xyz#gmail.com",
#After Removing read_only=true from AccountData
Request2
"EmailId" : "xyz#gmail.com",
"AccountData":{"AccountName":"TestAcc1"},
"CreateAccount": true,
"password" : "xyz"
Response
{"AccountData":{"non_field_errors":["Expected a list of items but got type \"dict\"."]}}
Request3
"EmailId" : "xyz#gmail.com",
"AccountData":[{"AccountName":"TestAcc1"}],
"CreateAccount": true,
"password" : "xyz"
Response
Got AttributeError when attempting to get a value for field `AccountData` on serializer `SignUpSerializer1`.
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 'AccountData'.
There is no response data fo AccountName in the response. And when I try print(User.objects.get(EmailId = serializer.data['AccountName'])) ====> None.
How should I get the field populated in the correct way in my response?
Thanks!

You need to specify source argument, since model's field called AccountName, not AccountData:
class SignUpSerializer1(ModelSerializer):
AccountData = AccountCreationSerializer(many=True, source="AccountName")

Related

Issue using nested serializer with django-rest-framework

I'm trying to create a nested serializer, UserLoginSerializer , composed of a UserSerializer and a NotificationSerializer, but I'm getting this error when it tries to serialize:
AttributeError: Got AttributeError when attempting to get a value for
field email on serializer UserSerializer. The serializer field
might be named incorrectly and not match any attribute or key on the
UserSerializer instance. Original exception text was:
'UserSerializer' object has no attribute 'email'.
Here is my models.py:
class Notification(models.Model):
kind = models.IntegerField(default=0)
message = models.CharField(max_length=256)
class User(AbstractUser):
username = models.CharField(max_length=150, blank=True, null=True)
email = models.EmailField(unique=True)
customer_id = models.UUIDField(default=uuid.uuid4, editable=False)
And my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = [
"id",
"first_name",
"last_name",
"email",
"customer_id"
]
class NotificationSerializer(serializers.ModelSerializer):
class Meta:
model = Notification
fields = [
"id",
"kind",
"message",
]
class UserLoginSerializer(serializers.Serializer):
user_info = UserSerializer(read_only=True)
notifications = NotificationSerializer(many=True, read_only=True)
The error occurs at the last line in this endpoint:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user),
"notifications": NotificationSerializer(notifications, many=True),
}
)
return Response(serializer.data)
What am I doing wrong?
You can use .data attribute.
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": UserSerializer(request.user).data,
"notifications": NotificationSerializer(notifications, many=True).data,
}
)
return Response(serializer.data)
You must pass the data, not the serializer objects themselves. The data argument allows you to pass in a dictionary of data that will be used by the inner serializers to create a serialized representation. In this case, you will pass in the serialized data from the UserSerializer and NotificationSerializer to the UserLoginSerializer, which then returns the final serialized representation of the data.
Or, you may pass user and notifications directly as such:
def get_login_info(self, request):
notifications = Notification.objects.filter(recipient=request.user)
serializer = UserLoginSerializer(
{
"user_info": request.user,
"notifications": notifications
}
)
return Response(serializer.data)
Django Rest Framework model serializers have a to_representation method which converts the model instances to their dictionary representation, so in this case, the UserLoginSerializer will automatically use the UserSerializer and NotificationSerializer to serialize the user and notifications data. You can modify/change this method's behaviour by overriding it.
I hope this helps.

How to check if json data has empty value or not in django

I have created a CRUD application in Django.
and want to know how I can check if the input JSON data is empty or not
for example:
I am getting a JSON input dictionary as
{'status':'abc','name':'xyz','address':'abc#123','city':'abc'}etc
I want to check if name is not empty and city is not empty.
I don't want a required field in model but to handle the JSON data.
If the JSON input is not valid i.e if username and email is valid then save the data into database else give a warning 400.
serializers.py
from rest_framework import serializers
from .models import Location
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields= '__all__'
views.py
#api_view(['POST'])
def locationAdd(request):
serializer = LocationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response("data entered successfully")
else:
return Response("data entered is not valid")
return Response(serializer.data)
models.py
class Location(models.Model):
status = models.CharField(max_length=100)
name = models.CharField(max_length=100)
address = models.CharField(max_length=100)
city = models.CharField(max_length=100)
postalCode = models.IntegerField(null=True)
state = models.CharField(max_length=100)
The most appropriate way would be to add extra_kwrgs in the Meta class of your serilalizer.
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ['name', 'city']
extra_kwrgs = {
'name' : {
'required' : True
},
'city' : {
'required' : True
}
}
If you want to implement some other type of validation or manipulation to the particular field then you can use def validate_<field_name> in your serializer.
Here is how it can be implemented -
class LocationSerializer(serializers.ModelSerializer):
class Meta:
model = Location
fields = ['name', 'city']
extra_kwrgs = {
'name' : {
'required' : True
},
'city' : {
'required' : True
}
}
def validate_name(self, name):
"""
This method is going to verify if the `name` string contains of all white spaces.
"""
if name.isspace():
raise serializer.ValidationError('The name field contain only spaces and no characters.')
return name
You will also have to modify your view to make it work with your serializer's configuration.
#api_view(['POST'])
def locationAdd(request):
serializer = LocationSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response("data entered successfully")
In your views this line serializer.is_valid(raise_exception=True) will terminate the execution and will raise the validation errors if the provided data is not correct.
For fields that you want to make required even though they are not in your model you can define a custom field on the serializer that is required to override the generated fields
class LocationSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=True)
city = serializers.CharField(required=True)
class Meta:
model = Location
fields = '__all__'
You can do that in the views.py file. Just check like below before calling the serializer
#api_view(['POST'])
def locationAdd(request):
if len(request.data.get('username')) == 0 and len(request.data.get('email')) == 0:
return Response(status=400)
serializer = LocationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response("data entered successfully")
else:
return Response("data entered is not valid")
return Response(serializer.data)
You can add any validations on your request data like this.
As much as validation in the api backend is reasonable and should always be there for defensive measures, I would also consider doing a check for an empty form on the frontend. That way you dont send unecessary http requests to the backend.

Give user access only to one field - Django

I am building an API using Django Rest Framework for my car-sharing app. I want to let not owner users to have access to update "participants" field in race, so they can join. Other fields should be available only to owner. I was reading about django-guardian, but i don't realy understand how to implement it. Here's my model:
from django.db import models
from django.contrib.auth.models import User
class Race(models.Model):
owner = models.ForeignKey("auth.User", related_name = 'races', on_delete=models.CASCADE)
origin_long = models.DecimalField(max_digits=8, decimal_places=3)
origin_lat = models.DecimalField(max_digits=8, decimal_places=3)
destination_long = models.DecimalField(max_digits=8, decimal_places=3)
destination_lat = models.DecimalField(max_digits=8, decimal_places=3)
start_time = models.TimeField(auto_now=False, auto_now_add=False)
participants = models.ManyToManyField(User,blank=True)
schedule = models.DurationField(blank=True,null=True)
subs = models.ManyToManyField(User, related_name='subs',blank=True)
cost = models.DecimalField(max_digits=5, decimal_places=2)
def __str__(self):
return self.user.get_full_name()
Thank you in advance.
I don't think Django has anyway to have field level permission by default.
But we can tweak and restrict the fields through the serializers.py and views.py .
In views.py
class RaceUpdateView(UpdateAPIView):
lookup_field = 'pk'
serializer_class = RaceUpdateSerializer
queryset = Race.objects.all()
permission_classes = [IsAuthenticated]
model = Race
def put(self, request, pk):
try:
try:
race_obj = self.get_object()
except Exception as error:
context = {'error': "Race Id does not exist", 'success': "false", 'message': 'Race Id does not exist.'}
return Response(context, status=status.HTTP_404_NOT_FOUND)
#I don't know how you are checking owner. So i kept it this way.
if request.user.id != race_obj.owner.id:
#passing the fields which are to be used by the serializer.
serializer = RaceUpdateSerializer(race_obj, data=request.data, partial=True, fields=('participants',))
else:
serializer = RaceUpdateSerializer(race_obj, data=request.data, partial=True)
if serializer.is_valid():
try:
serializer.save()
except Exception as error:
context = {"success": False, "message": "Update Failed. %s" % str(error), "error": str(error)}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
context = {"success": True, "message": "Updated Successful", "error": "", "data": serializer.data}
return Response(context, status=status.HTTP_200_OK)
context = {"success": False, "message": "Updated Failed, Invalid Input Data", "error": str(serializer.errors)}
return Response(context, status=status.HTTP_400_BAD_REQUEST)
except Exception as error:
context = {'error': str(error), 'success': "false", 'message': 'Failed To Update Race.'}
return Response(context, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
In serializers.py
class RaceUpdateSerializer(ModelSerializer):
class Meta:
model = Race
fields = '__all__'
def __init__(self, *args, **kwargs):
# Don't pass the 'fields' arg up to the superclass
fields = kwargs.pop('fields', None)
# Instantiate the superclass normally
super(RaceUpdateSerializer, self).__init__(*args, **kwargs)
if fields is not None:
# Drop any fields that are not specified in the `fields` argument.
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
This way only the mentioned fields which is called from the views.py will be used while updating.
serializer = RaceUpdateSerializer(race_obj, data=request.data, partial=True, fields=('participants',))
It will achieve the task that you are trying to do.
Note - You can allow multiple fields this way as well
serializer = RaceUpdateSerializer(race_obj, data=request.data, partial=True, fields=('field1','field2'))

Django Rest Framework serializing nested data

I am trying to serialize data in this Serializer but I am always getting the output {}
Here is my serializer:
class RelationshipSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
related_user = UserSerializer(read_only=True)
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
)
Here is my view:
related_user_id = body["related_user"]
related_user = models.User.objects.get(id=related_user_id)
user = self.get_object()
user_serializer = serializers.UserSerializer(user).data
related_user_serializer = serializers.UserSerializer(related_user).data
# user_serializer and related_user_serializer return valid data.
data = {"user": user_serializer, "related_user": related_user_serializer}
serializer = serializers.RelationshipSerializer(data=data)
serializer.is_valid() # valid
return Response(serializer.data)
I am getting the id of related_user from post request then getting the queryset of the actual object, and user is the id passed in the url: user/{1}/. I then serialize each user and create a dictionary to pass to RelationshipSerializer. The serializer returns valid but the response is {}, what did I do wrong?
class RelationshipSerializer(serializers.ModelSerializer):
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
)
views.py
user = self.get_object()
data = {"user": user.id, "related_user": request.data["related_user"]}
serializer = serializers.RelationshipSerializer(data=data)
if serializer.is_vaild():
serializer.save()
return Response(serializer.data)

AttributeError - When using Django NestedFields serializers

I've 2 models:-
class Users(models.Model):
first_name = models.CharField(max_length=255)
middle_name = models.CharField(max_length=255)
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users)
The user model & user address model. Following are the 2 serializers.
class UserAddressSerializer(ModelSerializer):
class Meta:
model = UserAddress
exclude = ('id', 'user')
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(many=True)
class Meta:
model = Users
fields = '__all__'
def create(self, validated_data):
address = validated_data.pop('address', [])
user = Users.objects.create(**validated_data)
for ad in address:
UserAddress.objects.create(user=user, **ad)
return user
The data I receive from the client is
{
"first_name": "string",
"last_name": "string",
"address": [{
"line1": "asd",
"country": "asd",
}],
}
This is how I create a new user and its corresponding address.
class UserCreate(GenericAPIView):
serializer_class = UserSerializer
def post(self, request, *args, **kwargs):
data = request.data
serializer = UserSerializer(data=data)
if not serializer.is_valid():
return
user = serializer.save()
response = {
'user_id': user.uuid
}
return
Now, upon getting the user details back, I receive an error saying
AttributeError: Got AttributeError when attempting to get a value for field `address` on serializer `UserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Users` instance.
Original exception text was: 'Users' object has no attribute 'address'.
This is how I get the details of the user, including the address.
class UserDetails(GenericAPIView):
queryset = Users.objects.all()
serializer_class = UserSerializer
lookup_field = 'uuid'
def get(self, request, uuid, *args, **kwargs):
user = Users.get_user(uuid)
if not user:
return
serializer = UserSerializer(instance=user)
return
I'd read this example of nested relationship, and am doing exactly the same way. why is the error coming up?
Also, can this code be shorten up more (in a nicer clean way) using some DRF mixins? If yes, then how?
I think the most simple solution for your case is: in model UserAddress add related_name='address'
class UserAddress(models.Model):
line1 = models.CharField(max_length=255)
country = models.CharField(max_length=255)
user = models.ForeignKey(Users, related_name='address')
# ^^^^^^^^^^^^^^^
or you can add sourse property in serializer:
class UserSerializer(ModelSerializer):
address = UserAddressSerializer(source='useraddress_set', many=True)
Serializer try to find attribute 'address' in the model User, but by default it is modelname underscore set (useraddress_set in your case), and you try other name, so you can set in the model or specify by source.
in the example you can look on models and find the related_name='tracks'

Categories

Resources