Django rest framework nested serialization not working properly - python

I have these serializers in my app:
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ('id',)
class DisciplineSerializer(serializers.ModelSerializer):
class Meta:
model = Discipline
fields = ('id',)
class WriteTeacherSerializer(serializers.ModelSerializer):
disciplines = DisciplineSerializer(many=True)
schedules = ScheduleSerializer(many=True)
class Meta:
model = Teacher
fields = ('phone_number', 'bio', 'price', 'disciplines', 'schedules')
depth = 1
def update(self, instance, validated_data):
print "\n"
#Debugging here
print validated_data
print "\n"
print instance.__dict__
print "\n"
instance.phone_number = validated_data['phone_number']
instance.bio = validated_data['bio']
instance.price = validated_data['price']
disciplines = validated_data.pop('disciplines')
schedules = validated_data.pop('schedules')
for discipline in disciplines:
try:
stored_discipline = Discipline.objects.get(id=discipline['id'])
instance.disciplines.add(stored_discipline)
except Discipline.DoesNotExist:
raise Http404
for schedule in schedules:
try:
stored_schedule = Schedule.objects.get(id=schedule['id'])
instance.schedules.add(stored_discipline)
except Discipline.DoesNotExist:
raise Http404
instance.save()
return instance
As you can see I am trying a nested serialization with the fields schedules and disciplines. I think I followed the documentation, but the nested serialization is not working when I test it. I printed the instance and validated_data objects and tested it on the shell.
I start the data in this format:
data = {u'phone_number': u'+99999999999', u'bio': u'BIO', u'price': 40, u'disciplines': [{'id': 1}], u'schedules': [{'id': 2}]}
I got a teacher instance and started the serializer like this:
serializer = WriteTeacherSerializer(teacher, data=data)
It shows True on a serializer.is_valid() call.
However when I try to save it the validated_data and the instance.__dict__ are like that:
#validated_data
{u'phone_number': u'+5584998727770', u'bio': u'BIO', u'price': 40, u'disciplines': [OrderedDict()], u'schedules': [OrderedDict()]}
#instance.__dict__
{'phone_number': u'', 'bio': u'', 'price': 50, 'profile_id': 2, '_state': <django.db.models.base.ModelState object at 0xb64a6bec>, 'id': 6}
They don't seem to notice the nested fields wich makes the update() method not work.
Am I doing something wrong?
Here is my Teacher Model as well:
class Teacher(models.Model):
price = models.IntegerField(default=50)
phone_regex = RegexValidator(regex=r'^\+?1?\d{9,15}$', message="Wrong phone number format.")
phone_number = models.CharField(validators=[phone_regex], max_length=15, blank=True)
profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
schedules = models.ManyToManyField(Schedule, related_name='schedules')
disciplines = models.ManyToManyField(Discipline, related_name='disciplines')
bio = models.CharField(max_length=200, blank=True)

If you are just sending IDs then you don't need to add the nested serializer, just specify the field name of the ForeignKey or ManyToManyField.
class WriteTeacherSerializer(serializers.ModelSerializer):
class Meta:
model = Teacher
fields = ('phone_number', 'bio', 'price', 'disciplines', 'schedules')
I am also wondering if it is because you have a depth=1 flag?
DRF doesn't support nested updates out of the box. You have to override the Serializer's update method, and write your own update logic, so you'd be seeing an error for this warning if you were sending nested data.

Related

The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance

I am using Django Rest and my request parameter contains:
[
{
"job_role": 2,
"technology": 1
},
{
"job_role": 1,
"technology": 1
},
{
"job_role": 2,
"technology": 1
}
]
My models are:
class Technology(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class JobRole(models.Model):
role_name = models.CharField(max_length=100)
def __str__(self):
return self.role_name
class ExpertPricing(models.Model):
role_name = models.OneToOneField(JobRole, related_name="role", on_delete=models.SET_NULL, null=True)
experience_in_years = models.PositiveBigIntegerField()
technology = models.OneToOneField(Technology, related_name="technology", on_delete=models.SET_NULL, null=True)
salary_per_month = models.PositiveBigIntegerField()
My view looks like this:
class PricingView(APIView):
def post(self, request):
datas = request.data
data_list = []
for data in datas:
job_role_id = data["job_role"]
technology_id = data["technology"]
job_role = JobRole.objects.get(pk=job_role_id)
technology = Technology.objects.get(pk=technology_id)
expert_pricing = ExpertPricing.objects.filter(role_name=job_role, technology=technology)
if expert_pricing:
data_list.append(expert_pricing)
serializer = ExpertPricingSerializer(data_list, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
serializers.py
class TechnologySerializer(serializers.ModelSerializer):
class Meta:
model = Technology
fields = ("id", "name")
class JobRoleSerializer(serializers.ModelSerializer):
class Meta:
model = JobRole
fields = ("id","role_name")
class ExpertPricingSerializer(serializers.ModelSerializer):
role = JobRoleSerializer(many=False, read_only=True)
technology = TechnologySerializer(many=False, read_only=True)
class Meta:
model = ExpertPricing
fields = "__all__"
I am unable to understand why data_list is not being serialized.
the error says:
AttributeError: Got AttributeError when attempting to get a value for field `experience_in_years` on serializer `ExpertPricingSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `QuerySet` instance.
Original exception text was: 'QuerySet' object has no attribute 'experience_in_years'.
Since you defined in your model that those two fields (experience_in_years and salary_per_month) can not be empty, it seems like you need to do one of these things:
Send experience_in_years and salary_per_month fields in your request too.
Give a default value to those fields
Make it null=True, blank=True
If you do 2 or 3 those solutions require migration, keep that in mind, after doing one of those things you should be good to go

Django Serializer showing "character": "Dictionary object (256)" as opposed to the actual character

I am using Django rest framework and I am trying to do a JOIN on a GET command.
I have the following view:
class CharacterView(APIView):
permission_classes = (AllowAny,)
def get(self, request, user_id):
character_saves = Char.objects.select_related('character').filter(
user_id=user_id)
serializer = CharacterSerializer(character_saves, many=True)
return Response({"characters": serializer.data})
And the following serializer:
class CharacterSerializer(serializers.Serializer):
character_id = serializers.IntegerField()
user_id = serializers.IntegerField()
active = serializers.BooleanField()
character = serializers.CharField()
def create(self, validated_data):
return Char.objects.update_or_create(
user_id=validated_data.pop('user_id'),
character_id=validated_data.pop('character_id'),
defaults=validated_data
)
Yet, I am getting the following data:
"characters": [
{
"character_id": 256,
"user_id": 1,
"active": true,
"character": "Dictionary object (256)"
},
{
"character_id": 260,
"user_id": 1,
"active": true,
"character": "Dictionary object (260)"
}
]
Instead of giving me the actual item I want, it gives me a dictionary object. This is almost correct, however I am guessing I am configuring something wrong in my query or serializer, preventing me from getting the raw value.
How do I get the actual value here, rather than "Dictionary object (260)"?
Char model:
class Char(models.Model):
character = models.ForeignKey(
Dictionary, on_delete=models.CASCADE)
user = models.ForeignKey(
User, on_delete=models.CASCADE)
active = models.BooleanField()
Dictionary model:
class Dictionary(models.Model):
traditional = models.CharField(max_length=50)
simplified = models.CharField(max_length=50)
pinyin_numbers = models.CharField(max_length=50)
pinyin_marks = models.CharField(max_length=50)
translation = models.TextField()
level = models.IntegerField()
class Meta:
db_table = 'dictionary'
tredzko's answer is correct, but you have asked in the comment box that you like to put several fields from dictionary into the serialized output, so for that part you can do like this,
Class DictionarySerializer(models.Model):
class Meta:
model = Dictionary
fields = (....) # Your specified fields goes here
class CharacterSerializer(serializers.Serializer):
character_id = serializers.IntegerField()
user_id = serializers.IntegerField()
active = serializers.BooleanField()
character = DictionarySerializer() # simply add this
EDIT: No need to override the create method.
Because you specified a CharField for the Dictionary relation, it's trying to get the string representation of your Dictionary model. One way you could fix it is to set the str representation on the model:
class Dictionary(models.Model):
...
def __str__(self):
return self.traditional
Alternatively, you should be able to alter the source of the character field on your serializer to instead pull from a property from the relation.
class CharacterSerializer(serializers.Serializer):
...
character = serializers.CharField(source="character.traditional")

Combining serializer and model functions

I have two serializers... MyRegisterSerializer inherits and extends a popular app/package, django-rest-auth, which connects to a fairly standard user table. I also have a Model and serializer for a custom app, TeamSerializer (a one-to-many relationship with users). When a user signs up, I would like them to be able to join a team at the same time, so I somehow need to create a team, return the team ID and then pass that ID to the RegisterSerializer, so that the ID of the team can be stored in the User table. I know I could make two calls, first to create the team and return the value, and then pass it to the register serializer, but is there a way to do this all in one serializer? I am a n00b at python, and cant find a great example of this, considering I have to return the get_cleaned_data() function as it is. Thank you!
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('id', 'name', 'logo', 'user')
class MyRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
def get_cleaned_data(self):
super(MyRegisterSerializer, self).get_cleaned_data()
return {
'team_id': <How do I get this value>
'username': self.validated_data.get('username', ''),
'position': self.validated_data.get('password1', ''),
'email': self.validated_data.get('email', ''),
'first_name': self.validated_data.get('first_name', ''),
'last_name': self.validated_data.get('last_name', '')
}
It depends on how you want to create the team:
1. The team is created by some other information:
You should be able to use this custom field:
from rest_framework.relations import PrimaryKeyRelatedField
class TeamPrimaryKeyRelatedField(PrimaryKeyRelatedField):
def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
obj, created = self.get_queryset().get_or_create(
pk=data,
defaults=get_team_data(),
)
return obj
except (TypeError, ValueError):
self.fail('incorrect_type', data_type=type(data).__name__)
And use it in your Serializer:
class MyRegisterSerializer(RegisterSerializer):
team = TeamPrimaryKeyRelatedField()
# ...
2. Use extra user input to create the team:
This looks like a perfect use case for writable nested serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ('id', 'name', 'logo', 'user')
class MyRegisterSerializer(RegisterSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
team = TeamSerializer()
def create(self, validated_data):
team_data = validated_data.pop('team')
# You could do this if the user is not necessary in the team object:
team = Team.objects.create(**team_data)
user = super().create(team=team, **validated_data)
# Otherwise:
user = super().create(**validated_data)
# Should this be a many-to-many relationship?
team = Team.objects.create(user=user, **team_data)
# I don't know if this works/you need it:
self.team = team
# Or it should be like this?
self.validated_data['team'] = team
return user
I'm not sure what exactly you need. Let me know if you need further help.

Django Rest: Correct data isn't sent to serializer with M2M model

I have a simple relational structure with projects containing several sequences with an intermediate meta model.
I can perform a GET request easily enough and it formats the data correctly. However, when I want to post the validated_data variable does not contain data formatted correctly, so I can't write a create/update method.
The data should look like:
{'name': 'something',
'seqrecords': [{
'id': 5,
'sequence': 'ACGG...AAAA',
'name': 'Z78529',
'order': 1
},
{
'id': 6,
'sequence': 'CGTA...ACCC',
'name': 'Z78527',
'order': 2
},
}
But instead it looks like this:
{'name': 'something',
'projectmeta_set': [
OrderedDict([('order', 1)]),
OrderedDict([('order', 2)]),
OrderedDict([('order', 3)])
]
}
Serializers:
class ProjectMetaSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField(source='sequence.id')
name = serializers.ReadOnlyField(source='sequence.name')
class Meta:
model = ProjectMeta
fields = ['id', 'name', 'order']
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
seqrecords = ProjectMetaSerializer(source='projectmeta_set', many=True)
class Meta:
model = Project
fields = ['id', 'name', 'seqrecords']
ReadOnlyField = ['id']
def create(self, validated_data):
project = Project(name=validated_data['name'])
project.save()
# This is where it all fails
for seq in validated_data['seqrecords']:
sequence = SeqRecord.objects.filter(id=seq['id'])
meta = ProjectMeta(project=project,
sequence=sequence,
order=seq['order'])
meta.save()
return project
class SeqRecordSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SeqRecord
fields = ['id', 'name', 'sequence']
read_only_fields = ['id']
Models:
class SeqRecord(models.Model):
name = models.CharField(max_length=50)
sequence = models.TextField()
class Project(models.Model):
name = models.CharField(max_length=50)
sequences = models.ManyToManyField(SeqRecord,
through='primer_suggestion.ProjectMeta')
class ProjectMeta(models.Model):
project = models.ForeignKey(Project)
sequence = models.ForeignKey(SeqRecord)
order = models.PositiveIntegerField()
View
class ProjectApiList(viewsets.ViewSetMixin, generics.ListCreateAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
Is there some way to change the validation of the data, so it returns the things I need or can I write the create and functions some other way?
To return the correct data format the function "to_internal" can be overridden in the ProjectSerializer, like this:
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
seqrecords = ProjectMetaSerializer(source='projectmeta_set', many=True)
class Meta:
model = Project
fields = ['id', 'name', 'seqrecords']
ReadOnlyField = ['id']
def to_internal_value(self, data):
''' Override standard method so validated data have seqrecords '''
context = super(ProjectSerializer, self).to_internal_value(data)
context['seqrecords'] = data['seqrecords']
return context
def validate(self, data):
...
def create(self, validated_data):
...
return project
This method is of course dependent on good a validation function for the "seqrecords" field in validate().
As you see in the ProjectMetaSerializer, the fields id and name are ReadOnlyFields. So you can't use them in post request.
class ProjectMetaSerializer(serializers.ModelSerializer):
seqrecords = SeqRecordSerializer(many=True)
class Meta:
model = ProjectMeta
fields = ['seqrecords',]
class ProjectSerializer(serializers.ModelSerializer):
project_meta = ProjectMetaSerializer(many=True)
class Meta:
model = Project
fields = ['id', 'name', 'project_meta']
ReadOnlyField = ['id']
def create(self, validated_data):
project = Project(name=validated_data['name'])
project.save()
# This is where it all fails
for seq in validated_data['seqrecords']:
sequence = SeqRecord.objects.filter(id=seq['id'])
meta = ProjectMeta(project=project,
sequence=sequence,
order=seq['order'])
meta.save()
return project
class SeqRecordSerializer(serializers.ModelSerializer):
class Meta:
model = SeqRecord
fields = ['id', 'name', 'sequence']

How to retrieve object attributes of an OneToOneField relation in Django-Rest-Framework

I'm trying to extend the User model with a OneToOneField, so I can add more fields to a user:
class Userattribs(models.Model):
user = models.OneToOneField(User)
passcode = models.IntegerField(default=0)
# about user
organisation = models.CharField(max_length=100, null=True)
description = models.CharField(max_length=300, null=True)
I also have the following model serializers:
class UserattribsSerializer(serializers.ModelSerializer):
class Meta:
model = Userattribs
fields = ('organisation', 'description')
class UserSerializer(serializers.ModelSerializer):
userattribs = UserattribsSerializer(required=True)
class Meta:
model = User
fields =('id', 'username', 'first_name', 'last_name', 'email', 'userattribs')
The problem I'm having is the serialisation of a User, does not include the 'userattribs' in the json response. I've spent hours googling and banging my head. I'd be eternally grateful if you help me out.
Thanks in advance!
I've added the views. One odd thing I noticed was that when I queried (using UserList) all data, I would get the Userattribs. However, when I retrieve a single item, I don't get the Userattribs
###################################################
### Views #########################################
###################################################
class UserList(generics.ListCreateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (permissions.AllowAny,)
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = (access_permission,)
I don't see any error in your code. Are you sure that your User objects have an Userattribs object.
Open your python shell with python manage.py shell and enter this to test your code :
#import your related models and serializer
from your_app.models import *
from your_app.serializer import *
# create new user
user = User.objects.create(username="test")
# create new user attrib linked to `user` object
user_attrib = Userattribs.objects.create(user=user, organisation="DJANGO")
# serialize your `user` object
user_serializer = UserSerializer(user)
# display your serialized data
user_serializer.data
# outputs : {'last_name': '', 'userattribs': OrderedDict([('organisation', 'DJANGO'), ('description', None)]), 'email': '', 'username': 'test', 'first_name': '', 'id': 3}
If it ok, the problem maybe come from your view.

Categories