Django rest related model not included - python

I have a TwitchChannel model which has a ForeignKey relationship to CustomUser.
class TwitchChannel(models.Model):
login = models.CharField(max_length=25)
display_name = models.CharField(max_length=25)
twitch_user_id = models.CharField(max_length=50)
email = models.EmailField(null=True, blank=True)
profile_image_url = models.URLField(null=True, blank=True)
access_token = models.CharField(default="none", max_length=100)
refresh_token = models.CharField(default="none", max_length=100)
live = models.BooleanField(default=False)
channel_data = JSONField()
created = models.DateTimeField(auto_now=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True)
def save(self, **kwargs):
make_password(self.access_token)
make_password(self.refresh_token)
super().save(**kwargs)
def __str__(self):
return self.display_name
def get_channel_url(self):
return f"https://twitch.tv/{self.login}"
In my UserSerializer I want to include this TwitchChannel when it exists. The following are my serializers.
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
twitch_channel = TwitchChannelSerializer(read_only=True, many=True)
def create(self, validated_data):
user = UserModel.objects.create(
email=validated_data["email"],
first_name=validated_data["first_name"],
last_name=validated_data["last_name"]
)
user.set_password(validated_data["password"])
user.save()
return user
def update(self, instance, validated_data):
if 'password' in validated_data:
password = validated_data.pop('password')
instance.set_password(password)
class Meta:
model = UserModel
depth = 3
fields = ( "id", "first_name", "last_name", "email", "password", "twitch_channel")
extra_kwargs = {"password": {"write_only": True, }}
class TwitchChannelSerializer(serializers.Serializer):
class Meta:
model = TwitchChannel
fields = (
'user_id',
'login',
'display_name',
'email',
'profile_image_url',
'access_token',
'refresh_token',
'live',
'channel_data',
'created',
'user'
)
However, when I do a request for the user the field isn't even included at all.
{
"id": 3,
"first_name": "Patrick",
"last_name": "Hanford",
"email": "testing#streambeacon.tv"
}
I get no error, but the field is non-existent.

You can use source argument to specify model's field name. By default django use modelname_set name for reverse foreign key relation, so you can use this:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
twitch_channel = TwitchChannelSerializer(read_only=True, many=True, source="twitchchannel_set")
Also you need to use ModelSerializer for both classes:
class TwitchChannelSerializer(serializers.ModelSerializer):

Related

Django Rest Framework error: {'user': [ErrorDetail(string='This field is required.', code='required')]}

Unable to add a new customer to the database..
I made a class Named customer that has a one-to-one relationship with a class named User that is an AbstractUser
I want to send the data through rest API so that I can create a new customer in the customer table and a new user that is One To One Related to the customer from the same view.
User Model
class User(AbstractUser):
# Add additional fields here
id = None
email = models.EmailField(max_length=254, primary_key=True)
name = models.CharField(max_length=100)
password = models.CharField(max_length=100)
is_patient = models.BooleanField(default=False)
is_doctor = models.BooleanField(default=False)
is_homesampler = models.BooleanField(default=False)
is_pathologist = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now=True)
first_name = None
last_name = None
username = None
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name', 'password']
objects = CustomUserManager()
def __str__(self):
return self.email
# Ensure that the password is hashed before saving it to the database
def save(self, *args, **kwargs):
self.password = make_password(self.password)
super(User, self).save(*args, **kwargs)
def has_perm(self, perm, obj=None):
return self.is_superuser
User Serializer
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
# fields = (['id', 'username', 'email', 'name'])
fields = '__all__'
customer Model
class customer(models.Model):
user = models.OneToOneField(
get_user_model(), on_delete=models.CASCADE, primary_key=True)
real = models.BooleanField(default=False)
def __str__(self):
return self.user.name
Customer Serializer
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
class Meta:
model = customer
fields = '__all__'
def create(self, validated_data):
user_data = validated_data.pop('user')
user = get_user_model().objects.create(**user_data)
user.is_Patient = True
customer = customer.objects.create(user=user, **validated_data)
return customer
Create Customer View
# Create add customer API
#api_view(['POST'])
def addCustomer(request):
customer_serializer = CustomerSerializer(data=request.data)
if(customer_serializer.is_valid()):
customer_serializer.save()
print(customer_serializer.errors)
return Response({'message': 'okay'})
Body of API Call
{
"email" : "test#test.com",
"password": "Abc"
}
So the question is how can I create a view so that I can create a new user and a customer using just one API Call
Your call body doesn't match CustomerSerializer.
CustomerSerializer fields are "user" and "rest", so you only can pass these two unless you do something like these:
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
user = get_user_model().objects.create(**{
"email": email,
"password": password
})
user.is_Patient = True
customer = customer.objects.create(user=user, **validated_data)
return customer
About the create method, it won't function correctly:
Because:
We shouldn't use create to create a new user instead we should use create_user More
I noticed that you removed the username so this method would be useless :)
After user.is_Patient = True, you forgot to save the user
The correct code would be:
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
user = get_user_model().objects.create(email=email)
user.set_password(password)
user.is_Patient = True
user.save()
customer = customer.objects.create(user=user, **validated_data)
return customer
NOTE 1:
# Ensure that the password is hashed before saving it to the database
def save(self, *args, **kwargs):
self.password = make_password(self.password)
super(User, self).save(*args, **kwargs)
It is the wrong approach to make_password here because whenever you make a change in your user, it would run.
the ideal approach would be using user.set_password("new_pass") whenever you get a new password from the user.
NOTE 2:
When you pass read_only to a serializer, this means would be ignored if you passed it as data to the serializer.
About write_only, it's the opposite of read_only. It would not be returned if you called serializer.data. For example, we only want to write to the password, and we won't want to read it from the serializer, so we made it write_only.
UPDATE: Adding a name
You have to add a write_only field and then pass it to create method
class CustomerSerializer(serializers.ModelSerializer):
userdata = UserSerializer(read_only=True, source='user')
email = serializers.EmailField(write_only=True)
password = serializers.CharField(write_only=True)
name = serializers.CharField(write_only=True)
class Meta:
model = customer
fields = ["userdata", "email", "password", "id", "real", "name"]
def create(self, validated_data):
email = validated_data.pop("email")
password = validated_data.pop("password")
name = validated_data.pop("name")
user = get_user_model().objects.create(email=email, name=name)
user.set_password(password)
user.is_Patient = True
user.save()
customer = customer.objects.create(user=user, **validated_data)
return customer

Post request with an object via serializer many=False

I am trying to make a POST request with an object for example this is how I send my request :
{
"title": "Haloween",
"body": " This is one of the greatest ones",
"grade_level": {
"id": 2,
"country": "UG"
},
"tags": [{"name": "Jamming"}]
}
So I wanted to post an object :
"grade_level": {
"id": 2,
"country": "UG"
}
and below is my Serializer I use :
class GradeClassSerializer(CountryFieldMixin, serializers.ModelSerializer):
"""GradeClass Serializer."""
class Meta:
model = ClassGrade
fields = ('id', 'grade', 'country', 'color_code', )
class PostSerializer(serializers.ModelSerializer):
"""Post Serializer"""
owner = UserProfile(read_only=True)
tags = TagSerializer(many=True)
comments = CommentSerializer(many=True, read_only=True)
slug = serializers.SlugField(read_only=True)
grade_level = GradeClassSerializer(many=False)
When I send the object grade_level , I cant seem to receive it it only receives the the id :
def create(self, validated_data):
"""Create a blog post in a customized way."""
grade_level = validated_data.pop('grade_level', {})
status = validated_data.pop('status', '')
post = Post.objects.create(**validated_data,
owner=self.context['request'].user)
if grade_level:
grade = ClassGrade.objects.get(id=grade_level['id'])
post.grade_level = grade
post.save()
return post
When I make a request, this is what happens :
KeyError: 'id'
The object comes with only an country without an id.
This is what grade_level = validated_data.pop('grade_level', {}) prints :
OrderedDict([('country', 'UG')])
How can get the id from the object.
NOTE:
id is not flagged as read_only
EDIT :
In the views.py below is the view :
class PostList(generics.ListCreateAPIView):
"""Blog post lists"""
queryset = Post.objects.filter(status=APPROVED)
serializer_class = serializers.PostSerializer
authentication_classes = (JWTAuthentication,)
permission_classes = (PostsProtectOrReadOnly, IsMentorOnly)
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data, context={
'request': request})
if serializer.is_valid():
serializer.save()
return response.Response(serializer.data,
status=status.HTTP_201_CREATED, )
return response.Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
Then models :
class ClassGrade(TimeStampedModel, models.Model):
"""ClassGrade is the class which Identifies the class or grade."""
grade = models.CharField(
_('Name'), max_length=150, null=True, blank=True)
country = CountryField()
color_code = ColorField(format='hexa', default='#33AFFF', null=True)
def __str__(self):
return self.grade
class Post(MainProcess, TimeStampedModel, models.Model):
"""Post model."""
title = models.CharField(_('Title'), max_length=100, blank=False,
null=False)
image = models.ImageField(_('Image'), upload_to='blog_images', null=True,
max_length=900)
body = models.TextField(_('Body'), blank=False)
description = models.CharField(_('Description'), max_length=400,
blank=True, null=True)
By default, DRF treats the id(PrimaryKey) inside ModelSerializer as read-only. So to override this behavior u can try PrimaryKeyRelatedField
class GradeClassSerializer(CountryFieldMixin, serializers.ModelSerializer):
"""GradeClass Serializer."""
id = serializers.PrimaryKeyRelatedField(queryset=ClassGrade.objects.all(),
required=True)
class Meta:
model = ClassGrade
fields = ('id', 'grade', 'country', 'color_code', )
So, by default, DRF will use the model fields in a ModelSerializer if you don’t define a field. Because the Id is an auto-created primary key (Django does this if you don’t explicitly override it) and Django assumes a primary key is read only, the id is omitted from the deserialized request

Incorrect type. Expected pk value, received str in DRF.(one to one field)

When I send post request with data in profile model at that time this error show.
Error
{
"user_name": [
"Incorrect type. Expected pk value, received str."
] }
I saw this answer but don't know how to implement SlugRelatedField in serializers(OneToOneField)
models.py:
class CustomUser(AbstractUser):
# username_validator = UnicodeUsernameValidator()
username = models.CharField(
max_length=80,
unique=True,
)
email = models.EmailField(
unique=True,
blank=True,
)
.....
def __str__(self):
return '%s' %(self.username)
class Profile(models.Model):
user_name = models.OneToOneField(to = CustomUser,on_delete=models.CASCADE)
full_name = models.CharField(null=True,max_length=15, blank=True)
public_name = models.CharField(null=True,max_length=15, blank=True)
....
serializers.py:
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['user_name','full_name','public_name']
views.py:
class ProfileApiview(CreateAPIView):
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
#Pradip - Have a look at this.
class ProfileSerializer(serializers.ModelSerializer):
user_name = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Profile
fields = ['user_name','full_name','public_name']
def create(self, validated_data):
user = self.context['request'].user
profile = Profile.objects.create(
user_name=user, full_name=validated_data['full_name'].............)
return profile

How to handle PUT request for a model with an extended user(onetoone field) relationship django rest framework

I have a profile model with a onetoone relationship with the django User model, I created an API endpoint which works fine for get and post request but when I try to do a put request to edit some part of the data, it gives me this error.
{
"user": [
"This field is required."
]
}
This is my Profile model
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
location = models.CharField(max_length=30, blank=True)
This is my Serializer class
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id' ,'username', 'first_name',
'last_name', 'email',
'password')
extra_kwargs = {
"password": {"write_only": True},
}
class ProfileSerializer(serializers.ModelSerializer):
"""
A student serializer to return the student details
"""
user = UserSerializer()
class Meta:
model = Profile
fields = ('uid', 'user', 'bio', 'location')
# read_only_fields = ('user',)
def update(self, instance, validated_data):
user_data = validated_data.pop('user')
user = instance.user
instance.bio = validated_data.get('bio', instance.bio)
instance.location = validated_data.get('location', instance.location)
instance.save()
user.username = user_data.get('username', user.username)
user.first_name = user_data.get('first_name', user.first_name)
user.last_name = user_data.get('last_name', user.last_name)
user.save()
return instance
And if there is obviously already a username in the response I want to perform a PUT request, and I don't want to change the username just some other data, it gives error { "user": { "username": [ "A user with that username already exists." ], "password": [ "This field is required." ] } }

Django Rest Framework - nested objects not being generated properly

I am trying to implement simple api in Django Rest Framework.
I have following models in models.py:
class Entry(BaseModel):
company_name = models.CharField(max_length=256, null=True, blank=True)
first_name = models.CharField(null=True, default=None, max_length=32)
last_name = models.CharField(null=True, default=None, max_length=32)
code = models.CharField(null=True, default=None, max_length=12)
class Meta:
db_table = 'entry'
class Admin(admin.ModelAdmin):
list_display = ('company_name', 'code')
list_display_links = ('company_name', )
ordering = ('-created',)
class EntryContactData(BaseModel):
entry = models.ForeignKey(Entry, related_name='contact')
email = models.CharField(max_length=256, null=True, blank=True)
website = models.CharField(max_length=64, null=True, blank=True)
phone = models.CharField(max_length=64, null=True, blank=True)
My API serializers.py:
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from core.models import Entry, EntryContactData
class EntryContactSerializer(serializers.ModelSerializer):
class Meta:
model = EntryContactData
fields = ('uuid', 'email', 'website', 'phone')
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(many=False, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')
And my API views:
from core.models import Entry
from .serializers import EntrySerializer
class EntryViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
queryset = Entry.objects.all()
def retrieve(self, request, pk=None):
queryset = Entry.objects.all()
entry = get_object_or_404(queryset, code=pk)
serializer = EntrySerializer(entry, context={'request': request})
return Response(serializer.data)
When I want to retrieve single entry its contact field is empty:
{
"uuid": "e6818508-a172-44e1-b927-3c087d2f9773",
"company_name": "COMPANY NAME",
"first_name": "FIRSTNAME",
"last_name": "LASTTNAME",
"contact": {}
}
So it doesn't contain any of fields defined in EntryContactSerializer
What am I doing wrong? How can I force it to return all fields included in serializer? Thank you guys.
Try setting many=True in EntrySerializer, and provide a source attribute to the serializer,
class EntrySerializer(serializers.ModelSerializer):
contact = EntryContactSerializer(source='contact', many=True, read_only=True)
class Meta:
model = Entry
fields = ('uuid', 'company_name', 'first_name', 'last_name', 'contact')

Categories