I am new to Django Rest Framework and checked some tutorials. Now I am trying to create my own structure which is like following. I want to create a user which is OK, then create a profile seperately.
models.py
class User(models.Model):
name = models.CharField(max_length=32)
surname = models.CharField(max_length=32)
facebook_id = models.TextField(null=True)
is_sms_verified = models.BooleanField(default=False)
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(default=timezone.now)
status = models.BooleanField(default=1)
def __str__(self):
return self.name+" "+self.surname
class Profile(models.Model):
user = models.ForeignKey('User',on_delete=models.CASCADE)
email = models.CharField(max_length=32)
birthday = models.DateField(null=True)
bio = models.TextField(null=True)
points = models.IntegerField(default=0)
created = models.DateTimeField(default=timezone.now)
updated = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.user.name+ " " + self.user.surname
serializers.py
class UserSerializer(serializers.ModelSerializer):
class Meta:
model=User
fields = ('id','name','surname','facebook_id','is_sms_verified',)
read_only_fields = ('created','updated')
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model=Profile
fields=('id','user','email','birthday','bio','points')
read_only_fields = ('created','updated')
views.py
#api_view(['POST'])
def profile_create(request):
serializer = ProfileSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return JsonResponse(serializer.data, status = status.HTTP_201_CREATED)
return JsonResponse(serializer.errors , status= status.HTTP_400_BAD_REQUEST)
data I'm trying to post
{
"user_id": {
"id": 2
},
"email": "xxx#gmail.com",
"birthday": "1991-05-28",
"bio": "qudur",
"points": 31
}
The error I get;
NOT NULL constraint failed: core_profile.user_id
Where am I doing wrong? Thanks!
Your ProfileSerializer has user as readonly. So you need to change that. I would suggest doing it like this
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model=Profile
fields=('id','user','email','birthday','gender','bio','points')
read_only_fields = ('created','updated')
def to_representation(self, instance):
self.fields['user'] = UserSerializer(read_only=True)
return super(ProfileSerializer, self).to_representation(instance)
If you do it this you could provide your user as plain id for POST
{
"user": 2,
"email": "xxx#gmail.com",
"birthday": "1991-05-28",
"bio": "qudur",
"points": 31
}
And when you will read data it will look like this
{
"user": {
"id": 2,
"name": "Name",
"surname": "Surname",
...
},
"email": "xxx#gmail.com",
"birthday": "1991-05-28",
"bio": "qudur",
"points": 31
}
I've noticed Super() throws an error the way it's mentioned above in the awnser:
return super(ProfileSerializer,self).to_representation(instance)
Error: Type error, object must be an instance or subtype of type
Try the Following:
Models.py
class Program(models.Model):
name = models.CharField(max_length=225)
cost = models.IntegerField(default=0)
description = models.TextField(default="", max_length=555)
class UserProgram(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
program = models.ForeignKey(Program, on_delete=models.CASCADE, related_name="program")
Serializers.py
class ProgramSerializers(serializers.ModelSerializer):
class Meta:
model = Program
fields = "__all__"
class UserProgramSerializers(serializers.ModelSerializer):
class Meta:
model = UserProgram
fields = "__all__"
#IMPORTANT PART
def to_representation(self, instance):
response = super().to_representation(instance)
response['program'] = ProgramSerializers(instance.program).data
return response
Views.py
class UserProgramViewset(viewsets.ModelViewSet):
permission_classes = [
permissions.IsAuthenticated
]
serializer_class = UserProgramSerializers
def get_queryset(self):
return UserProgram.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
When you call the GET request the following should be the output:
GET Request Output
When you call the POST request you only need to pass the programID and not the whole JSON dictionary!
Hope this helped.
Related
I have two serializers:
class AlbumImageSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField('get_url')
def get_url(self, obj):
return obj.image.url
class Meta:
model = AlbumImage
fields = ['id', 'url']
class PhotoAlbumSerializer(serializers.ModelSerializer):
photos = AlbumImageSerializer(many=True, read_only=True)
class Meta:
model = PhotoAlbum
fields = ('id', 'name', 'photos')
And i read that to display full url of image it's enough to pass a request context to serializer call, like:
serialized = SomeSerializer(some, context={"request": request})
And when you use ViewSet you don't need to do anything, DRF will automatically pass the request context while initializing the serializer.
I am using ViewSets like:
class PhotoAlbumViewSet(mixins.CreateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
serializer_class = PhotoAlbumSerializer
but still my get-request returns response like:
"url": "/media/images/photo.jpg"
How can i get
"url": "127.0.0.1:8000/media/images/photo.jpg"
In my case?
My models
class PhotoAlbum(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, auto_created=True)
name = models.CharField(max_length=50, verbose_name='Pet name')
class AlbumImage(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, auto_created=True)
album = models.ForeignKey(PhotoAlbum, on_delete=models.CASCADE, related_name='photos')
image = models.ImageField(upload_to='images/', height_field=None, width_field=None, max_length=100, blank=True)
Another endpoint where print url return the response like 127.0.0.1:8000/media/UUID/photo.jpg:
#action(methods=['post'], detail=True)
def photo(self, request, pk):
file = request.data['file']
album = PhotoAlbum.objects.get(pk=pk)
alb_img = AlbumImage(album=album, image=file)
alb_img.save()
return Response({'id': pk,
'url': alb_img.image)})
Just do not try to override the url field, let the framework take care of it for you:
serializers.py
class AlbumImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.AlbumImage
fields = ['id', 'url']
class PhotoAlbumSerializer(serializers.ModelSerializer):
photos = AlbumImageSerializer(many=True, read_only=True)
class Meta:
model = models.Pet
fields = ('id', 'name', 'photos')
sample output:
[
{
"id": 1,
"name": "Pet name.",
"photos": [
{
"id": 2,
"url": "http://localhost:8000/media/random_image_one.png"
},
{
"id": 3,
"url": "http://localhost:8000/media/random_image_two.png"
}
]
}
]
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
I am new to DRF and could not figure out how to append an object to the many to many field without deleting the previous one.
I am using PATCH to update the field MONITORS however the previous value gets substituted by the actual one. I want to append it.
API GET is:
{
"id": 1,
"created": "2018-05-02T23:43:07.605000Z",
"modified": "2021-04-03T10:25:12.280896Z",
"companies_house_id": "",
"name": "Ellison PLC",
"description": "",
"date_founded": "2018-04-28",
"country": 4,
"creator": 7,
"monitors": [
3
]
}
after PATCH {"monitors":[11]} I get:
{
"id": 1,
"created": "2018-05-02T23:43:07.605000Z",
"modified": "2021-04-03T10:25:12.280896Z",
"companies_house_id": "",
"name": "Ellison PLC",
"description": "",
"date_founded": "2018-04-28",
"country": 4,
"creator": 7,
"monitors": [
11
]
}
I want the final GET API to be "monitors": [3, 11]
models.py
class Company(TimeStampedModel):
companies_house_id = models.CharField(max_length=8, blank=True)
name = models.CharField(max_length=200, blank=True)
description = models.TextField(blank=True)
date_founded = models.DateField(null=True, blank=True)
country = models.ForeignKey(Country, on_delete=models.PROTECT, blank=True)
creator = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET_NULL,
null=True,
related_name='companies_created'
)
monitors = models.ManyToManyField(
settings.AUTH_USER_MODEL,
blank=True,
related_name='companies_monitored',
help_text='Users who want to be notified of updates to this company'
)
def __unicode__(self):
return u'{0}'.format(self.name)
serializers.py
from rest_framework import serializers
from .models import Company
from django.contrib.auth import get_user_model
class CompanySerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = '__all__'
class UserSerializer(serializers.ModelSerializer):
companies_monitored = CompanySerializer(many=True, read_only=True)
# companies_moniotred = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = get_user_model()
fields = ('id', 'username', 'companies_monitored')
views.py
class CompanyDetailsView(generics.RetrieveUpdateAPIView):
queryset = Company.objects.all()
serializer_class = CompanySerializer
class UserList(generics.ListCreateAPIView):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
def get_object(self, username):
username = get_object_or_404(get_user_model(), username=username)
return username
def get(self, request, username):
username = self.get_object(username)
serializer = UserSerializer(username)
return Response(serializer.data)
urls.py
from django.conf.urls import url
from django.urls import path
from . import views
from .views import CompanyDetailsView, UserList, UserDetail
urlpatterns = [
path('details/<int:pk>/', CompanyDetailsView.as_view(), name='company_details'),
path('users/<str:username>/', UserDetail.as_view(), name='profile_view'),
]
Use different serializer for PATCH method.
class CompanyDetailsView(generics.RetrieveUpdateAPIView):
queryset = Company.objects.all()
serializer_class = CompanySerializer
def get_serializer_class(self):
if self.request.method == 'PATCH':
return CompanyPatchSerializer
return self.serializer_class
and handle what you need in your serializer
class CompanyPatchSerializer(serializers.ModelSerializer):
class Meta:
model = Company
fields = '__all__'
#transaction.atomic
def update(self, instance, validated_data):
if 'monitors' in validated_data:
monitor_ids = validated_data.pop('monitors')
# todo: handle get_or_create
return super().update(instance, validated_data)
I don't like implicit m2m models so my suggestion would be to add through model as well.
I do not recommend overriding patch method, but below code should do the job
def patch(self, request, *args, **kwargs):
request.data._mutable = True
new_monitors = request.data.get('monitors')
existing_monitors = Company.objects.get(kwargs.get('id')).values_list('monitors__id', flat=True)
all_monitors = existing_monitors + new_monitors
request.data.update({'monitors': set(all_monitors)})
return self.partial_update(request, *args, **kwargs)
Is there any way to display the name of the user in the "likedBy" section of the view, instead of the user id? Using django rest framework
From view I get , ignore comments:
[
{
"id": 3,
"title": "ddsa",
"content": "dsadsa",
"created": "2021-02-10T08:07:42.758400Z",
"updated": "2021-02-10T08:07:42.758400Z",
"author": 1,
"category": [
{
"pk": 1,
"name": "Life"
}
],
"likedBy": [
1
],
"comments": [
{
"id": 2,
"content": "ghfa",
"created": "2021-02-10T08:08:02.407950Z",
"post": 3,
"author": 1,
"likedBy": [
1
]
}
]
}
]
Views.py:
class PostViewSet(FlexFieldsMixin, generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permit_list_expands = ['category', 'comments']
Models.py
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
Serializers.py:
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
How serialize ManyToMany field to display text values
Given that you are not serializing a relation, but rather an attribute of your model which is related to your user, I believe you have to use a serializer.SerializerMethodField(). This allows you to do the following:
class PostSerializer(FlexFieldsModelSerializer):
liked_by = serializers.SerializerMethodField(method_name="get_user_likes")
class Meta:
model = Post
fields = (
"title",
"content",
"category",
"author",
"created",
"update",
"liked_by"
)
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
}
#classmethod
def get_user_likes(obj):
# do whatever you like here, but I tend to call a
# method on my model to keep my serializer file
# nice and tidy
# you'll need to define a UserSerializer
return UserSerializer(obj.get_user_likes(), many=True, read_only=True)
And in your Post model:
class Post(models.Model):
title = models.CharField(max_length=255)
content = models.TextField()
category = models.ManyToManyField(Category, related_name='posts')
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likedBy = models.ManyToManyField(User, related_name='posts', blank=True)
class Meta:
ordering = ['-created']
def get_user_likes(self):
return self.likedBy.all()
You can of course define the full method in your serializer, but as I said I like to keep the serializer as clean as possible and put all methods associated with models in my models.py.
#classmethod
def get_user_likes(obj):
# you'll need to define a UserSerializer
return UserSerializer(obj.likedBy.all(), many=True, read_only=True)
So you can set
likedBy = serializers.ReadOnlyField(source='get_likedBy')
in your PostSerializer class
and define function in your Post model class like below:
#property
def get_likedBy(self):
liked_by = []
for user in self.users_liked_post.all():
liked_by.append(user.name)
return liked_by
just use correct related_name instead of users_liked_post
If you add likedBy to your expandable fields, and then add ?expand=likedBy to your url, it should give you all the information that you outline in the UserSerializer, or write a new serializer named LikedBySerializer. Also as a general rule, try not to use '__all__' it's a good way to leak data. Happy coding!
class LikedBySerializer(FlexFieldsModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializer(FlexFieldsModelSerializer):
class Meta:
model = Post
fields = '__all__'
expandable_fields = {
'category': ('blogApi.CategorySerializer', {'many': True}),
'comments': ('blogApi.CommentSerializer', {'many': True}),
'likedBy': ('blogApi.LikedBySerializer', {'many': True}),
}
I have this M2M relation with through model as
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Please note that, I have extra fields date_joined and invite_reason in the through model.
Now, I want to serialize the Group queryset using DRF and thus I choose the below serializer setup.
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
class GroupSerializer(serializers.ModelSerializer):
members = PersonSerializer(read_only=True, many=True)
class Meta:
model = Group
fields = "__all__"
and it is returning the following response,
[
{
"id": 1,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Developer"
},
{
"id": 2,
"members": [
{
"id": 1,
"name": "Jerin"
}
],
"name": "Team Lead"
}
]
Here, the members field returning the Person information, which is perfect.
But,
How can I add the date_joined and invite_reason field/info into the members field of the JSON response?
class PersonSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = "__all__"
def serialize_membership(self, person_instance):
# simple method to serialize the through model fields
membership_instance = person_instance \
.membership_set \
.filter(group=self.context["group_instance"]) \
.first()
if membership_instance:
return MembershipSerializer(membership_instance).data
return {}
def to_representation(self, instance):
rep = super().to_representation(instance)
return {**rep, **self.serialize_membership(instance)}
class MembershipSerializer(serializers.ModelSerializer): # create new serializer to serialize the through model fields
class Meta:
model = Membership
fields = ("date_joined", "invite_reason")
class GroupSerializer(serializers.ModelSerializer):
members = serializers.SerializerMethodField() # use `SerializerMethodField`, can be used to pass context data
def get_members(self, group):
return PersonSerializer(
group.members.all(),
many=True,
context={"group_instance": group} # should pass this `group` instance as context variable for filtering
).data
class Meta:
model = Group
fields = "__all__"
Notes:
Override the to_representation(...) method of PersonSerializer to inject extra data into the members field of the JSON
We need person instance/pk and group instance/pk to identify the Membership instance to be serialized. For that, we have used the serializer context to pass essential data