Django foreign key rest framework - python

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}),
}

Related

Can't get full url of Image Field Django Rest

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"
}
]
}
]

Django REST RetrieveAPIView combining querysets

I'm trying to build a queryset which combines two query results, namely from Category and Course. Every Course has a Category foreign key. Is there a way to add the respective Courses to each Category?
Example:
{
"id": 61,
"name": "fgfdf",
"courses":
{
"id": 1,
"category": 61,
"title": "mytitle"
"active": true
},
{
...
}
}
Url
path('dict/<pk>/', DictView.as_view(), name='detail')
Models
class Category(models.Model):
name = models.CharField(max_length=255, blank=False, null=False)
class Course(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
title = models.CharField(max_length=255, blank=False, null=False)
active = models.BooleanField(default=True)
View
This is what I imagined but it's obviously incorrect, I've done some research but I couldn't find what I needed.
class DictView(RetrieveAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
def get_queryset(self):
queryset = Category.objects.all()
courses = list(Course.objects.filter(category=pk))
queryset['courses'] = courses;
return queryset
One way is defining serializers like this:
class CourseSerializer(serializers.ModelSerializer):
class Meta:
model = Course
fields = "__all__"
class CategorySerializer(serializers.ModelSerializer):
courses = CourseSerializer(source='course_set', many=True)
class Meta:
model = Category
fields = "__all__"
Then, you don't need to override get_queryset anymore.
If you wish to apply filters for courses, say you only want active courses, you can do the following:
class CategorySerializer(serializers.ModelSerializer):
courses = serializers.SerializerMethodField()
def get_courses(self, obj):
active_courses = obj.course_set.filter(active=True)
return CourseSerializer(active_courset, many=True).data
class Meta:
model = Category
fields = "__all__"

Add expandable field to Serializer on Self Related Model

I have got a model of User Details which is Self Related to create the relationship between two users.
Model
class AdvDetails(models.Model):
user_id = models.UUIDField(primary_key=True,default=uuid.uuid4,editable=False)
title = models.CharField(max_length=5, choices=[('Mr', 'Mr'), ('Ms', 'Ms'), ('Mrs', 'Mrs'), ('Dr', 'Dr'), ('NA', '')], default='NA')
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
email_id = models.EmailField(null=True, blank=True, default=None)
parent_profile = models.ForeignKey("self", null=True, blank=True)
View Set
class AdvDetailsViewSet(viewsets.ModelViewSet):
serializer_class = AdvDetailsSerializer
filter_backends = (filters.SearchFilter,DjangoFilterBackend, filters.OrderingFilter)
filter_fields = ('email_id,'parent_profile')
search_fields = ( '^first_name',)
def get_queryset(self):
return AdvDetails.objects.all()
Serializer
class AdvDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = AdvDetails
fields = '__all__'
This structure facilitates me to help get the data in the below format
{
"user_id": "055cbde6-10ea-4558-86fc-1b42624ce760",
"title": "Mr",
"first_name": "foo",
"last_name": "bar",
"email_id" : "foo#bar.com"
"parent_profile":"6c429d4c-9fb4-42e5-9d7d-9fc782f81fb0"
}
I would like to modify the serializer in such a way that I would get the data of the parent profile's email_id as below
{
"user_id": "055cbde6-10ea-4558-86fc-1b42624ce760",
"title": "Mr",
"first_name": "foo",
"last_name": "bar",
"email_id" : "foo#bar.com",
"parent_profile":"6c429d4c-9fb4-42e5-9d7d-9fc782f81fb0",
"parent_email_id" : "parent#email.com"
}
Any help is highly appreciated. I've tried PrimaryKeyRelatedField but it was not solving my problem.
I made a package that allows expanding fields dynamically, on-demand per request:
https://github.com/rsinger86/drf-flex-fields
For your case:
class AdvDetailsSerializer(FlexFieldsModelSerializer):
class Meta:
model = AdvDetails
fields = ('user_id', 'title', 'first_name', 'last_name', 'email_id', 'parent_profile', 'parent_email_id' )
expandable_fields = {'parent_profile': '<app_name>.AdvDetailsSerializer'}
Replace <app_name> with the name of the Django app that defines the serializer, so it can be loaded lazily.
If instead you would like to statically expand a field, you can define that field as a nested serializer:
class AdvDetailsSerializer(serializers.ModelSerializer):
parent_profile = ProfileProfileSerializer()
class Meta:
model = AdvDetails
fields = (
'user_id',
'title',
'first_name',
'last_name',
'email_id',
'parent_profile',
'parent_email_id'
)

Use A Serializer Class Inside Other One In Django

I want to have a serializers that use two model at once (If it possible)
models.py
class Club(models.Model):
id = models.AutoField(primary_key=True)
clubname = models.CharField(max_length=50, blank=True, null=True)
location = models.CharField(max_length=50, blank=True, null=True)
scores = models.IntegerField(blank=True, null=True)
serializers.py
class ShowAllClubSerializer(serializers.ModelSerializer):
class Meta:
model = Club
fields
class ShowClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
views.py
#api_view(["GET", ])
#permission_classes((IsAuthenticated, ))
def show_all_clubs_view(request):
if request.method == "GET":
clubs = Club.objects.all()
if clubs:
for club in clubs:
pictures = Clubpictures.objects.filter(clubid=club.id)
serializer1 = ShowAllClubSerializer(club)
serializer2 = ShowClubPictures(pictures[0])
return Response(serializer1.data, status=status.HTTP_200_OK)
# return Response(serializer2.data, status=status.HTTP_200_OK)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
Now I Have These In serializers1 and serializers2 Separately:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
}
{
"picture": "/media/images/Screenshot.png"
}
How can I take something like this in result:
{
"clubname": "Club Name",
"location": "Somewhere",
"scores": 5,
"picture": "/media/images/Screenshot.png"
}
You can use nested serializers to achieve that.
Your ClubSerializer would look something like this:
class ClubSerializer(serializers.ModelSerializer):
pictures = ClubPictureSerializer(many=True)
class Meta:
model = Club
fields = ('clubname', 'location', 'scores', 'pictures')
class ClubPictures(serializers.ModelSerializer):
class Meta:
model = Clubpictures
fields = ['picture']
assuming you have a ForeignKey from the ClubPicture to your Club with a related_name of pictures.
Also, on your view, you don't need to loop through Club.objects.all() and serialize each object individually - ModelSerializer/Serializer accept a many=True parameters that handles multiple objects already.

Django Rest Framework - Post Foreign Key

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.

Categories