depth = 1 doesn't work properly and it's saves Null in ManyToManyField and ForeignKey fields in Django Rest Framework - json

after adding depth = 1 doesn't work properly
=> models.py file
class State(models.Model):
state_name = models.CharField(max_length = 30, unique=True)
def __unicode__(self):
return str(self.state_name)
class City(models.Model):
state = models.ForeignKey(State, related_name='state_city')
city_name = models.CharField(max_length = 30)
def __unicode__(self):
return str(self.city_name)
class Meta:
ordering = ('city_name',)
unique_together = ('state', 'city_name',)
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', related_name='snippets')
state = models.ForeignKey(State,blank=True,null=True)
city = models.ManyToManyField(City)
=> serializers.py file
class StateSerializer(serializers.ModelSerializer):
class Meta:
model = State
class CitySerializer(serializers.ModelSerializer):
state_name = serializers.CharField(source='state.state_name', read_only=True)
class Meta:
model = City
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username', read_only=True)
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'owner', 'state', 'city')
depth = 1
I have added ForeignKey and ManyToManyField fields in state and city respectively. It doesn't save values in SnippetSerializer while added depth = 1 in Meta Class (it saves Null value in state and city fields). When I add depth = 1 JSON showing related fields as it should be but it doesn't work properly while add new Snippet. Without depth = 1 it works fine.
I have complex database where tables has many ManyToMany and ForeignKey related fields. Please give me suggestion so I can get related data in JSON.
I have djangorestframework-3.1.2 version. I have used latest version too but same problem. please give me solution and thanks in advance.

I faced the same problem and managed to solve it. Since the problem is with the depth, I just change the depth value in the init method.
class SnippetSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username', read_only=True)
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'owner', 'state', 'city')
depth = 1
def __init__(self, *args, **kwargs):
super(SnippetSerializer, self).__init__(*args, **kwargs)
request = self.context.get('request')
if request and request.method=='POST':
self.Meta.depth = 0
else:
self.Meta.depth = 1
In the code above, I changed the depth dynamically according to what type of request that I made.
But, this is the workaround that I found myself, I'm not sure if this is the best practice but it solve the problem with just little modification.

depth is only for representation (http://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization). If you want to create/update the related fields too you have to overwrite the create/update methods in the serializer (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers).
Greetings.

Related

Update multiple model data through one serializer

Please go through the description, I tried to describe everything i've encountered while trying to solve this issue.
I have two models, User and DoctorProfile. User model has OneToOne relation with DoctorProfile. I'm trying to update data of both model through one serializer. I've combined two models into one serilzer like below:
class DoctorProfileFields(serializers.ModelSerializer):
"""this will be used as value of profile key in DoctorProfileSerializer"""
class Meta:
model = DoctorProfile
fields = ('doctor_type', 'title', 'date_of_birth', 'registration_number', 'gender', 'city', 'country', )
class DoctorProfileSerializer(serializers.ModelSerializer):
"""retrieve, update and delete profile"""
profile = DoctorProfileFields(source='*')
class Meta:
model = User
fields = ('name', 'avatar', 'profile', )
#transaction.atomic
def update(self, instance, validated_data):
ModelClass = self.Meta.model
profile = validated_data.pop('profile', {})
ModelClass.objects.filter(id=instance.id).update(**validated_data)
if profile:
DoctorProfile.objects.filter(owner=instance).update(**profile)
new_instance = ModelClass.objects.get(id = instance.id)
return new_instance
When I send request with GET method, the DoctorProfileSerializer returns nested data(Combining two models User and DoctorProfile) in the desired fashion.
But when I try to update both models through this serializer, it returns error saying User has no field named 'doctor_type'.
Let's have a look at the JSON i'm trying to send:
{
"name": "Dr. Strange updated twice",
"profile" : {
"doctor_type": "PSYCHIATRIST"
}
}
Let's have a look at how the serializer is receiving the JSON:
{
"name": "Maruf updated trice",
"doctor_type": "PSYCHIATRIST"
}
Models:
class CustomUser(AbstractBaseUser, PermissionsMixin):
class Types(models.TextChoices):
DOCTOR = "DOCTOR", "Doctor"
PATIENT = "PATIENT", "Patient"
#Type of user
type = models.CharField(_("Type"), max_length=50, choices=Types.choices, null=True, blank=False)
avatar = models.ImageField(upload_to="avatars/", null=True, blank=True)
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = CustomBaseUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'type'] #email is required by default
def get_full_name(self):
return self.name
def __str__(self):
return self.email
class DoctorProfile(models.Model):
"""Model for Doctors profile"""
class DoctorType(models.TextChoices):
"""Doctor will choose profession category from enum"""
PSYCHIATRIST = "PSYCHIATRIST", "Psychiatrist"
PSYCHOLOGIST = "PSYCHOLOGIST", "Psychologist"
DERMATOLOGIST = "DERMATOLOGIST", "Dermatologist"
SEXUAL_HEALTH = "SEXUAL HEALTH", "Sexual health"
GYNECOLOGIST = "GYNECOLOGIST", "Gynecologist"
INTERNAL_MEDICINE = "INTERNAL MEDICINE", "Internal medicine"
DEVELOPMENTAL_THERAPIST = "DEVELOPMENTAL THERAPIST", "Developmental therapist"
owner = models.OneToOneField(
CustomUser,
on_delete=models.CASCADE,
related_name='doctor_profile'
)
doctor_type = models.CharField(
_("Profession Type"),
max_length=70,
choices=DoctorType.choices,
null=True,
blank=False
)
title = models.IntegerField(_('Title'), default=1, choices=TITLES)
date_of_birth = models.DateField(null=True, blank=False)
gender = models.IntegerField(_('Gender'), default=1, choices=GENDERS)
registration_number = models.IntegerField(_('Registration Number'), null=True, blank=False)
city = models.CharField(_('City'), max_length=255, null=True, blank=True)
country = models.CharField(_('Country'), max_length=255, null=True, blank=True)
def __str__(self):
return f'profile-{self.id}-{self.title} {self.owner.get_full_name()}'
How do I know that the serializer is getting wrong JSON? I debugged the validated_data in the DoctorProfileSerializer and it's showing that it's a flat JSON, there's no key named profile.
I'm assuming the problem is with the source that I've added in the DoctorProfileSerializer. But if I don't use the source the get method returns the following error
Got AttributeError when attempting to get a value for field profile on serializer (DoctorProfileSerializer).
Please let me know if it's solvable also if it's a good approach to do it this way?
Ok, sorry if my answer is too long but let me try to answer step by step,
Models:
class DoctorProfile(models.Model):
# everything as it is
# except I feel comfortable using ForeignKey :D
owner = models.ForeignKey(
CustomUser,
on_delete=models.CASCADE,
related_name='doctor_profile'
)
# everything as it is
class CustomUser(AbstractBaseUser, PermissionsMixin):
# as it is
Serializers:
class DoctorProfileSerializer(serializers.ModelSerializer):
"""Serializer for DoctorProfile."""
class Meta(object):
model = DoctorProfile
fields = [
'id',
'doctor_type',
'title',
'date_of_birth',
'registration_number',
'gender',
'city',
'country',
]
read_only_fields = [
'id',
]
class CustomUserSerializer(serializers.ModelSerializer):
"""Serializer for DoctorProfile."""
# here I'm renaming the related object exactly as the
# related name you've provided on model
doctor_profile = DoctorProfileSerializer(many=False)
class Meta(object):
model = CustomUser
fields = [
'name',
'avatar',
'doctor_profile',
]
read_only_fields = [
'id',
]
def update(self, instance, validated_data):
# instance is the current row of CustomUser
# validated_data is the new incoming data
# use validated_data.pop('doctor_profile') to extract
# doctor_profile data and do whatever is needed on
# DoctorProfile model
# compare them and perform your update method
# as you wish on the DoctorProfile model
# object after updating models, you can query the total
# object again before returning if you want
return updated_object
View:
class CustomUserAPIView(RetrieveUpdateAPIView):
"""CustomUserAPIView."""
permission_classes = [IsAuthenticated]
model = CustomUser
serializer_class = CustomUserSerializer
lookup_field = 'id'
#for returning logged in user info only
def get_queryset(self):
return CustomUser.objects.filter(id=self.request.user.id).first()
def update(self, request, *args, **kwargs):
"""Update override."""
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(
instance,
data=request.data,
partial=partial,
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
custom_user_obj = CustomUser.objects.filter(
id=instance.id,
).first()
serializer = CustomUserSerializer(custom_user_obj)
return Response(serializer.data)
Run the migration and let me know if you are getting the expected output on GET method. For UPDATE method if you face any problem let me know I will update the answer accordingly right away.
For keeping all the Django Rest Framework related docs handy, use this link
https://www.cdrf.co/

How do I access a method from my Model in Django in React

In the following model...
class Question(models.Model):
question_text = models.CharField(max_length=200)
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
pub_at = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f"{self.question_text}"
def validity(self):
total_likes = self.likes + self.dislikes
if total_likes != 0:
return (self.likes / total_likes) * 100
else:
return 100
I want to be able to access Question.objects.get(pk=1).validity() assuming that pk=1 exists in this case. In python shell I can do this easily. But how do I do this using React. I am able to get all my questions and the fields in React without a problem but I don't think I have a way to access the validity method I created.
In this case I would suggest the following. First, remove the property from the model:
# models.py
class Question(models.Model):
question_text = models.CharField(max_length=200)
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
pub_at = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f"{self.question_text}"
Then add a SerializerMethodField (docs) to your serializer. It is read-only and can be used to pass computed values to your views:
# serializers.py
class QuestionSerializer(serializers.ModelSerializer):
validity = serializers.SerializerMethodField()
class Meta:
model = Question
fields = ['question_text', 'likes', 'dislikes', 'pub_at', 'category', 'validity']
def get_validity(self, instance):
total_likes = instance.likes + instance.dislikes
# Your approach is not wrong. This is a more explicit way of dealing with that particular error type
try:
return (instance.likes / total_likes) * 100
except ZeroDivisionError:
return 100
Bear in mind that the Foreign Key category will be serialized as its database unique id value (Primary Key) in this case.
You might want to use the #property decorator so that you can access the value the same way you would access any of the other fields on your Question model:
class Question(models.Model):
question_text = models.CharField(max_length=200)
likes = models.IntegerField(default=0)
dislikes = models.IntegerField(default=0)
pub_at = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL, null=True)
def __str__(self):
return f"{self.question_text}"
#property
def validity(self):
total_likes = self.likes + self.dislikes
percentage = (self.likes / total_likes) * 100
return percentage
Explanations can be found in the docs or here. Keep in mind that it will not be saved like the other attributes as columns on the database when you run migrations.
I am answering my own question here I found a solution to. Although, #property does work when rendering using a simple Django template when using React and rendering json responses validity is still not available.
In my serializers.py file I did the following...
class QuestionSerializer(serializers.ModelSerializer):
validity = serializers.ReadOnlyField()
class Meta:
model = Question
fields = '__all__'
Take away the #property from the models as it is no longer needed. This has worked for me and you can go to the Django rest_framework or test it in your React application to see if you have access to this.
I would like to know if there are any issues doing this and/or a better way. I was also trying to do validity = serializers.Field() instead of validity = serializers.ReadOnlyField() but got an error saying I needed a Field.to_representation() that takes in self, value as positional arguments.
What arguments exactly do I pass in here. I tried self, Question.validity and did not work. I am not sure what I am doing here.
As an update the method in the model I updated to...
def validity(self):
total_likes = self.likes + self.dislikes
if total_likes != 0:
return (self.likes / total_likes) * 100
else:
return 100
I did not notice before and does not really matter for the question but division by zero is not allowed being that by default division by zero will always occur.

Why is my Django serializer telling me an attribute doesn't exist when I see it defined in my model?

I'm using Python 3.7 and the Django rest framework to serialize some models into JSOn data. I have this
data = {
'articlestats': ArticleStatSerializer(articlestats, many=True).data,
and then I have defined the following serializers ...
class LabelSerializer(serializers.ModelSerializer):
class Meta:
model = Label
fields = ['name']
...
class ArticleSerializer(serializers.ModelSerializer):
label = LabelSerializer()
class Meta:
model = Article
fields = ['id', 'title', 'path', 'url', 'label']
class ArticleStatSerializer(serializers.ModelSerializer):
article = ArticleSerializer()
class Meta:
model = ArticleStat
fields = ['id', 'article', 'score']
I have defined my Label model like so ...
class Label(models.Model):
name = models.CharField(max_length=200)
def __str__(self):
return self.name
class Meta:
unique_together = ("name",)
but I'm getting this error when Django processes my serialize line ...
AttributeError: Got AttributeError when attempting to get a value for field `name` on serializer `LabelSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `str` instance.
Original exception text was: 'str' object has no attribute 'name'.
Not sure why it's complaining. The "name" attribute is right there. What else should I be doing?
Edit: Models asked for ...
class ArticleStat(models.Model):
objects = ArticleStatManager()
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')
score = models.FloatField(default=0, null=False)
class Article(models.Model):
objects = ArticleManager()
title = models.TextField(default='', null=False)
path = models.TextField(default='', null=False)
url = models.TextField(default='', null=False)
label = models.TextField(default='', null=True)
created_on = models.DateTimeField(db_index=True, default=datetime.now)
In Article model the label field is TextField, not any kind of related fields. But in your serializer, it expects a related field.
class ArticleSerializer(serializers.ModelSerializer):
label = LabelSerializer()
class Meta:
model = Article
fields = ['id', 'title', 'path', 'url', 'label']

How to do an update method for a nested django rest framework APi Boolean ? [OnetoOneField]

So i have been researching about how to update the nested serializer with onetoonefield. However it has not been able to solve my problem. As i am still new to django rest framework, i am still inexperience about what is the problem as i never done an API before.
models.py
class Membership(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
membership = models.BooleanField(default=False)
serializers.py
class MembershipSerializer(serializers.ModelSerializer):
class Meta:
model = Membership
fields = ('membership',)
class UserSerializer(serializers.ModelSerializer):
membership = MembershipSerializer(many=False)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password', 'first_name', 'last_name', 'is_staff', 'membership',)
read_only_fields = ('id',)
def create(self, validated_data):
membership_data = validated_data.pop('membership')
user = User.objects.create(**validated_data)
Membership.objects.create(user=user, **membership_data)
return user
def update(self, instance, validated_data):
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.password = validated_data.get('password', instance.password)
instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.is_staff = validated_data.get('is_staff', instance.is_staff)
instance.save()
membership_data = validated_data.get('membership')
membership_id = membership_data.get('id', None)
if membership_id:
membership_item = Membership.objects.get(id=membership_id, membership=instance)
membership_item.membership = membership_data.get('membership', membership_item.name)
membership_item.user = membership_data.get('user', membership_item.user)
membership_item.save()
return instance
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_permissions(self):
# allow non-authenticated user to create
return (AllowAny() if self.request.method == 'POST'
else permissions.IsStaffOrTargetUser()),
screenshot of api
https://i.imgur.com/dDqthRu.png
As you can see above, my membership is null, i have no idea why so i tested with is_staff to check and it is using false like a normal Booleanfield. This has make me wonder what is wrong with my models for the membership boolean field.
Main problem
As i am using a boolean field, i was trying to get the user membership to be updated. So i try to use PUT method and the result is nothing has change after i check the membership box and click on PUT.
And if i just want to update the username, i have to check on the membership box else it will give me this:
https://i.imgur.com/KpzHIsE.png
I have been checking online for several solution and none of them has work for me with the update method. I am also puzzle by the null value in the api for the booleanfield membership.

How to implement ForeignKey with Django serializer

I have this models:
class Discipline(models.Model):
name = models.CharField(max_length=200)
class Lesson(models.Model):
discipline = models.ForeignKey(Discipline, on_delete=models.CASCADE, null=True)
date = models.DateField()
regular_slot = models.BooleanField(default=False)
And these serializers:
class DisciplineSerializer(serializers.ModelSerializer):
class Meta:
model = Discipline
fields = ('name')
class LessonSerializer(serializers.ModelSerializer):
discipline = serializers.RelatedField(source='Discipline', read_only=True);
class Meta:
model = Lesson
fields = ('discipline', 'date', 'regular_slot')
I have a view to process a JSON request and to save the data:
def cours_list(request):
if request.method == 'POST':
data = JSONParser().parse(request)
serializer = LessonSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
My JSON request is as follows:
{"discipline":"Mathematiques","date":"2017-12-03"}
However, I have an error saying that:
'Lesson' object has no attribute 'Discipline'
I believe this is because attribute discipline in Lesson refers only to the id and is not of type Discipline. I could'nt find how to solve this. Is the way I define foreignKey is not correct ?
How to insert a foreignKey reference here while I receive a Discipline object from the JSON request ?
I'm going to help you also with this :)
class DisciplineSerializer(serializers.ModelSerializer):
class Meta:
model = Discipline
fields = ('name')
class LessonSerializer(serializers.ModelSerializer):
discipline = DiscliplineSerializer()
class Meta:
model = Lesson
fields = ('discipline', 'date', 'regular_slot')
Doing this is enough as far as I know.
Edit: Reason of your error is that you write "source="Discipline"" but there is no field named Discipline in your Lesson model. Therefore, it gives an error.