serializers.py
class JobSerializer(serializers.ModelSerializer):
# image = Base64ImageField(max_length=None,
# use_url=True)
# applicant = serializers.ForeignKe
applicant = serializers.StringRelatedField(many=True)
email = serializers.SerializerMethodField("get_username_from_user")
company_name = serializers.SerializerMethodField("get_company_name_from_user")
class Meta:
model = Jobs
fields = ['company_name', 'email', 'title', 'desc', 'image', 'price', 'category', 'applicant']
# extra_kwargs = {"email": {"required": False}}
def get_username_from_user(self, jobs):
email = jobs.user.email
return email
def get_company_name_from_user(self, jobs):
company_name = jobs.user.company_name
return company_name
views.py
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def api_detail_jobs_view(request, id):
try:
jobs = Jobs.objects.get(id=id)
except Jobs.DoesNotExist:
data = {}
data['response'] = "Job does not exist"
return Response(data, status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
serializer = JobSerializer(jobs)
user = request.user
if user == serializer.email:
data = {}
auth_show = serializer
data['title'] = auth_show.title
data['applicant'] = auth_show.applicant
return Response(data)
else:
no_auth_show = serializer
data = {}
data['title'] = no_auth_show.title
return Response(data)
here is serializers.py in which 'email' is included. i know i am missing something very clear but it took hours to realise :) so any help will be appriciated
i am trying to show 'applicants' only to users who owns the 'job' but i can't pass 'email' from serializer in to the view. I can't pass any attribute from serializer in to data dict.
As shown in the documentation of DRF, after serializing an object you can access its fields via the data attribute. So it would look like serializer.data['email'].
And one extra tip, consider using the Django shortcut get_object_or_404() instead of that try/except block. Good luck!
Related
I´m trying to create an instance of a serializer on a POST request, but it is ignoring the model instance im passing as the first argument
if request.method == 'POST':
if string_pk in reviewed_user_pk:
reviewed_user = User.objects.get(pk=user_pk)
review = Review(author=user, reviewed_user=reviewed_user)
serializer = CreateReviewSerializer(review, data=request.data)
I get user instance from the request:
try:
user = request.user
except user.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
The problem here is that the instance of Review which has both user instances (author and reviewed_user) is being ignored by the ReviewSerializer, here is the serializer:
class CreateReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Review
fields = ['author', 'reviewed_user','title', 'rating', 'comment', 'date_published']
The oter fields in request.data are being serialized but not the Review instance, what can be causing this problem? the error i get from serializer.errors is the following:
{
"author": [
"This field is required."
],
"reviewed_user": [
"This field is required."
]
}
Here is the complete function view:
#api_view(['POST'])
#permission_classes((IsAuthenticated,))
def api_create_review_view(request, user_pk): #user_pk is the pk of the reviewed_user
try:
user = request.user
except user.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
reviewed_user_pk = user.worked_with.split(',')
string_pk = str(user_pk)
data = {
}
if request.method == 'POST':
if string_pk in reviewed_user_pk:
reviewed_user = User.objects.get(pk=user_pk)
review = Review(author=user, reviewed_user=reviewed_user)
serializer = ReviewSerializer(review, data=request.data)
if serializer.is_valid():
serializer.save()
reviews_count = reviewed_user.reviews_count
rating = ((reviewed_user.rating * reviews_count) / (reviews_count + 1)) + ((serializer.rating) / (reviews_count + 1))
reviews_count += 1
reviewed_user.rating = rating
reviewed_user.reviews_count = reviews_count
reviewed_user.save()
return Response(serializer.data)
data = serializer.errors
return Response(data)
else:
data = {
'forbidden':'users have not worked together'
}
return Response(data=data)
And here is the Review model:
class Review(models.Model):
reviewed_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='reviewed_user')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='author')
rating = models.IntegerField(default=5)
title = models.CharField(max_length=50)
comment = models.CharField(max_length=500)
date_published = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Right now im not worried that the code inside the if statement that checks if the serializer is valid works, i just want to know how can i pass the instance of Review with both user instances inside to the serializer so the serializer is valid
if you are try to create a new instance of Review. Try this two method, i am not sure which one can work.
first way, change the request.data:
from copy import deepcopy
reviewed_user = User.objects.get(pk=user_pk)
serializer_data = deepcopy(request.data)
serializer_data['user'] = user
serializer_data['reviewed_user'] = reviewed_user
serializer = ReviewSerializer(serializer_data)
if serializer.is_valid():
serializer.save()
second way,just change serializer to partial with set partial=partial in Serializer:
reviewed_user = User.objects.get(pk=user_pk)
review = Review(author=user, reviewed_user=reviewed_user)
serializer = ReviewSerializer(review, data=request.data, partial=partial)
if serializer.is_valid():
third way, not validate user and reviewed_user in serializer, just save it,
remove 'author', 'reviewed_user' in serializer:
class CreateReviewSerializer(serializers.ModelSerializer):
class Meta:
model = Review
fields = ['title', 'rating', 'comment', 'date_published']
reviewed_user = User.objects.get(pk=user_pk)
serializer = ReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save(author=user, reviewed_user=reviewed_user) # save user and reviewed_user here
I'm creating a newsfeed in an APP.
A user is logged in and sees the posting of other users.
Therefore I need two user models (user + auth_users)
Now I want to add a boolean field that shows if a post is already liked or not.
I already looked at the documentation and other posts here but I can´t find a solution.
The auth_user is shown in the response but I can´t included it in the get_already_liked function
class NewsPostSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer(read_only=True)
auth_user = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
attachments = AttachmentSerializer(read_only=True, many=True)
already_liked = serializers.SerializerMethodField()
def get_already_liked(self, request):
liking_kwargs = {
'post_id': request.id,
'user_id': self.auth_user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False
class Meta:
model = Post
read_only_fields = (
'id', "user", 'creation_time_stamp', 'auth_user', 'ready_liked',
)
fields = (
'id', 'user', 'creation_time_stamp', 'last_update_time_stamp',
'description', 'attachments', 'already_liked', 'auth_user',
)
UPDATE:
In another post I found a solution. My code looks now like this and works:
class NewsPostSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer(read_only=True)
attachments = PostAttachmentSerializer(read_only=True, many=True)
already_liked = serializers.SerializerMethodField()
def get_already_liked(self, obj):
user = self.context['request'].user.id
liking_kwargs = {
'post_id': obj.id,
'user_id': user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False
class Meta:
model = Post
read_only_fields = (
'id', "user", 'creation_time_stamp', 'attachments', 'already_liked',
)
fields = (
'id', 'user', 'creation_time_stamp', 'last_update_time_stamp',
'description', 'attachments', 'already_liked',
)
Thanks to Marco and Shakil
I would change the approach here a little bit. For the like counter I'd use something like a model in the database that has a foreign key to the picture and another foreign key to the user, with that in place you can use the reverse relations between the models. For instance:
class Post(models.Model):
...
...
def number_of_likes(self):
return len(self.likes.all()) # dont know if sintax is right, but the idea is to get all the instance of likes that is related to this post. (reverse relation)
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE ,related_name='likes')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='all_likes')
So, what I recommend is to insert the counter of likes inside the model of the user, and then you can start playing around with this in the serializers.
Your serializerMethodField's arguments are wrong. This should be like this
def get_already_liked(self, obj):
request = self.context.get('request')
liking_kwargs = {
'post_id': request.id, // I have a doubt, is this request.data['id']
'user_id': obj.auth_user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False
# models.py
class Post(models.Model):
content = models.TextField(blank=True, default='')
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class PostImage(models.Model):
image = models.ImageField(upload_to=unique_upload)
post = models.ForeignKey(
Post, related_name='images', on_delete=models.CASCADE)
This is my model set up for a basic scenario where a user can enter content or upload images as their posts.
I want to bundle my logic to handle creating a post with either content or images or both.
I first started playing around with GenericViewSet and CreateViewSet but was images was never being passed to my serializer.
# views.py
class CreatePostViewSet(generics.CreateAPIView /* viewsets.GenericViewSet */):
permission_classes = (IsAuthenticated,)
queryset = Post.objects.order_by('id')
serializer_class = CreatePostSerializer
def create(self, request, *args, **kwargs):
data = {}
print(request.data)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(created_by=request.user)
# post = serializer.instance
# print(post)
# for im in post.images.all():
# im.save(post=post)
# print(post.images.all())
return Response(data,
status=status.HTTP_201_CREATED,
headers=self.get_success_headers(serializer.data))
# serializers.py
class PostImageSerializer(serializers.ModelSerializer):
class Meta:
model = PostImage
fields = ('id', 'url', 'image', 'post',)
read_only_fields = ('post',)
depth = 1
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def create(self, validated_data):
# validated_data['images'] is always []
print(validated_data)
raise
images is always [] when I pass it to a serializer, but it does exist in request.data['images'] as [<TemporaryUploadedFile: 1 - 5H5hHgY.png (image/png)>, ...
I was hoping to use ModelSerializer to help auto-resolve the ImageField.
# CreatePostSerializer serializers breaks down to
CreatePostSerializer():
id = UUIDField(read_only=True)
url = HyperlinkedIdentityField(view_name='post-detail')
content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
images = PostImageSerializer(many=True, required=False):
id = UUIDField(read_only=True)
url = HyperlinkedIdentityField(view_name='postimage-detail')
image = ImageField(max_length=100)
post = NestedSerializer(read_only=True):
id = UUIDField(read_only=True)
content = CharField(allow_blank=True, required=False, style={'base_template': 'textarea.html'})
created_by = PrimaryKeyRelatedField(queryset=User.objects.all())
It think request.data['images'] will need to be changed slightly because your PostImageSerializer will be expecting an object containing the "image" key, whereas you are passing the list of TemporaryUploadedFile.
Given request.data['images'] you could do something like the following in your view before you pass the data to the serializer:
images_list: List[TemporaryUploadedFile] = request.data.pop("images")
images = []
for image in images_list:
images.append({
"image": image,
})
request.data["images"] = images
So we are transforming your list of TemporaryUploadedFiles into a list of objects with the image key.
:edit: So you don't want to transform your data at the view to be compatible with the serializer? Then you can change the serializer to be compatible with the data, this involves customizing the create and update methods, I'm just going to show you how to override the create method for now.
class CreatePostSerializer(serializers.ModelSerializer):
images = serializers.ImageField(many=True)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def create(self, validated_data):
images = validated_data.pop("images")
post = super().create(validated_data)
for image in images:
serializer = PostImageSerializer(data={"image": image, "post": post.pk}, context=self.context)
serializer.is_valid()
serializer.save()
return post
So you don't want to override the data in the request and you don't want to customize the serializers create method? Change how the serializer converts your initial data into validated data with the validate method (I think this works for nested serializers but its untested):
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'url', 'content', 'images',)
read_only_fields = ('created_by',)
depth = 1
def validate(self, attrs):
images_list = attrs.pop("images")
images = []
for image in images_list:
images.append({
"image": image,
})
attrs["images"] = images
return attrs
So, I was able to get it to work with #ARJMP's suggestion.
# views.py
class CreatePostViewSet(generics.CreateAPIView):
# authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = Post.objects.order_by('id')
serializer_class = CreatePostSerializer
def create(self, request, *args, **kwargs):
data = {}
print(request.data)
images = [{'image': i} for i in request.data.pop('images', [])]
serializer = self.get_serializer(
data={'content': request.data['content'], 'images': images})
serializer.is_valid(raise_exception=True)
post = serializer.save(created_by=request.user)
# self.perform_create(serializer)
data['post'] = serializer.data
return Response(data,
status=status.HTTP_201_CREATED,
headers=self.get_success_headers(serializer.data))
# serializers.py
class CreatePostSerializer(serializers.ModelSerializer):
images = PostImageSerializer(many=True, required=False)
class Meta:
model = Post
fields = ('id', 'content', 'images',
'is_private', 'created_by',)
read_only_fields = ('view_count', 'created',)
depth = 1
def create(self, validated_data):
images = validated_data.pop('images', [])
p = Post.objects.create(**validated_data)
for im in images:
pi = PostImage.objects.create(image=im['image'], post=p)
return p
My thing is this seems rather convoluted to get it to work. A lot of manipulating it myself. I was really hoping to leverage more of the "magic" stuff that gets done with ModelSerializer and CreateAPIView.
Are there better approaches to doing this?
Running into a little snag here with my DRF backend.
I am populating fields with choices on certain models.
I have a foreign key requirement on one model. When I create the model I want to save it under the foreign id.
When I request the models, I want the model with whatever the choice field maps to.
I was able to do this with SerializerMethodField, however when I try to create a model, I get a 400 error because the block is not valid. If I remove the SerializerMethodField, I can save, but get the number stored in the db from the request.
Any help would be appreciated.
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = serializers.BlockSerializer(data=data, context={'request': request})
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkoutGoalSerializer(serializers.ModelSerializer):
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
The above code returns the correct choice, but I can't save under it. Remove the goal = WorkoutGoalSerializer() and it saves but doesn't return the mapped choice.
I think this will work like a charm,
class WorkoutGoalSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'request' in self.context and self.context['request'].method == 'GET':
self.fields['goal'] = serializers.SerializerMethodField(read_only=True, source='get_goal')
class Meta:
model = apps.get_model('backend', 'WorkoutGoal')
fields = ('goal',)
goal = serializers.SerializerMethodField(read_only=True, source='get_goal') # remove this line
def get_goal(self, obj):
return dict(WorkoutGoal.GOALS).get(obj.goal)
How this Work?
It will re-initiate the goal field with SerializerMethodField, if the reuested method is GET.
Remember one thing, you should remove the line,
goal = serializers.SerializerMethodField(read_only=True, source='get_goal')
serializers.py
class BlockCreateSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
class BlockSerializer(serializers.ModelSerializer):
workout_count = serializers.IntegerField(required=False)
completed_workouts = serializers.IntegerField(required=False)
goal = WorkoutGoalSerializer()
class Meta:
model = apps.get_model('backend', 'Block')
read_only_fields = ('workout_count', 'completed_workouts')
fields = read_only_fields + ('id', 'name', 'user', 'created', 'goal')
views.py
class BlockViewSet(ModelViewSet):
model = apps.get_model('backend', 'Block')
queryset = model.objects.all()
serializer_class = serializers.BlockSerializer
permissions = ('All',)
def get_serializer_class(self):
if self.action == 'create':
return serializers.BlockCreateSerializer
else:
return self.serializer_class
def create(self, request, format=None):
data = request.data
data['user'] = request.user.id
data['goal'] = WorkoutGoal.objects.get(goal=data['goal']).id
block = self.get_serializer(data=data)
if block.is_valid():
new_block = block.save()
return Response({'block': {'name': new_block.name, 'id': new_block.id}}, status=status.HTTP_201_CREATED)
else:
return Response(block.errors, status=status.HTTP_400_BAD_REQUEST)
override get_serializer_class to return different serializer_class for create and other action(list\retrieve\update\partial_update)
I'm working on a User Preferences viewset in DJANGO REST API in which a user can get a list of preferences along with updating the preferences. In Postman i can get the user's preferences, but when i go to 'put' i'm getting the following error: Integrity Error --NOT NULL constraint failed: pugorugh_userpref.age --any reason this might be happening?
The UserPref Model is below:
class UserPref(models.Model):
user = models.ForeignKey(User)
age = models.CharField(choices=AGE, max_length=7, default='b,y,a,s')
gender = models.CharField(choices=GENDER_PREF, max_length=3, default='m,f')
size = models.CharField(choices=SIZE_PREF, max_length=8, default='s,m,l,xl')
def __str__(self):
return '{} preferences'.format(self.user)
def create_user_preference(sender, **kwargs):
user = kwargs['instance']
if kwargs['created']:
user_pref = UserPref(user=user)
user_pref.save()
post_save.connect(create_user_preference, sender=User)
Here is my ViewSet:
class UserPrefViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
"""View to get and update User Preferences."""
permission_classes = (permissions.IsAuthenticated,)
queryset = models.UserPref.objects.all()
serializer_class = serializers.UserPrefSerializer
# /api/user/preferences/
#list_route(methods=['get', 'put'])
def preferences(self, request, pk=None):
user = request.user
user_pref = models.UserPref.objects.get(user=user)
if request.method == 'PUT':
data = request.data
user_pref.age = data.get('age')
user_pref.gender = data.get('gender')
user_pref.size = data.get('size')
user_pref.save()
serializer = serializers.UserPrefSerializer(user_pref)
return Response(serializer.data)
and SERIALIZER
class UserPrefSerializer(serializers.ModelSerializer):
extra_kwargs = {
'user': {'write_only': True}
}
class Meta:
fields = (
'age',
'gender',
'size'
)
model = models.UserPref
Looks like PUT data doesnt contain age value. Since age field is not nullable, blank age value raise error.
Try to fix this:
user_pref.age = data.get('age') or user_pref.age
...
user_pref.save()
this allows not to change age if value not in request data.