Before asking this question, I have seen the following links but they don't help me at all:
pass extra arguments to serializer
pass request context to serializer from viewset
pass context from one serializer to another
I have an author model that has foreign key to default django user model:
apps/author/models.py
class Author(models.Model):
user = models.OneToOneField(
User,
related_name='author',
on_delete=models.CASCADE,
default="",
)
is_author = models.BooleanField(
default=True,
)
full_name = models.CharField(
max_length=100,
default="",
)
def __str__(self):
return self.user.username
Post model has a foreign key to Author.
apps/posts/models.py
class Post(models.Model):
author = models.ForeignKey(
Author,
related_name="posts",
on_delete=models.CASCADE,
)
title = models.TextField(
null=True,
blank=True,
)
content = models.TextField(
null=True,
blank=True,
)
is_draft = models.BooleanField(
default=True
)
created_at = models.DateTimeField(
auto_now_add=True,
null=True,
)
published_at = models.DateField(
null=True,
blank=True,
default=None,
)
def __str__(self):
return str(self.id) + ", " + self.title
Problem Definition: In order to create a new post, I am getting the current user from self.request.user in views, and pass it to the PostSerializer. But whenever I want to create a new post using the following request to localhost:8000/posts/ I have got an error:
# I also added JWT authorization header to the postman! and it doesn't have any problem at this level
{
"title": "",
"content": ""
}
error
This is what I have done in apps/posts/views.py:
def get_serializer_context(self):
context = super().get_serializer_context()
context["user"] = self.request.user
context["author"] = Author.objects.get(user=context["user"])
print(context["author"])
return context
print(context["author"]) works well and prints out the current author. The problem is that I can't get it in serializers.
class PostSerializer(serializers.ModelSerializer):
# author = serializers.SerializerMethodField('get_author')
#
# def get_author(self, obj):
# print('current author', self.context["author"])
# return self.context["author"]
class Meta:
model = Post
fields = '__all__'
extra_fields = ['author']
#def create(self, validated_data):
#print(self.context["author"])
#print(self.context)
PS: The comments are the ways I have tried but the error is still occurred. How can I fix the problem?
Maybe you should add "required=False" in author field of PostSerializer, this will avoid "this field is required" error.
class PostSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(required=False)
And what view do you use? If you use GenericAPIView and its subclasses, context will pass to serializer, the default get_serializer_context will pass request by default. if not you should pass context manually.
class PostSerializer(serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(required=False)
def create(self, validated_data):
request = self.context.get('request')
author = Author.objects.get(user=request.user)
# ....
class PostView(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = PostSerializer
def create(self, request, *args, **kwargs):
super().create(request, *args, **kwargs)
UPDATE:
I was wrong, there is a simpler solution: https://stackoverflow.com/a/38167148/7285863
That should be simplest way.
class PostViewSet(viewsets.ModelViewSet):
# ... other implementations
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data={"author": request.user.author.id, **request.data})
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def update(self, request, pk, *args, **kwargs):
try:
post = Post.objects.get(pk=pk)
except Post.DoesNotExist:
raise NotFound("Post doesn't exist.")
if post.author != request.user.author:
raise PermissionDenied('Permission Denied!')
serializer = PostSerializer(post, data={"author": request.user.author.id, **request.data})
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
It's default method except passing author data.
You may need to check user has author relation.
Related
I don't feel like I get an idea on how it should work...
I do have 2 models:
Group is my custom model I wanna save.
class Group(models.Model):
name = models.CharField(max_length=40)
subject = models.CharField(max_length=40)
user = models.ForeignKey('User', on_delete=models.CASCADE)
User is a standard user model for the application. I'm skipping UserManager for now. Simply said User can have multiple groups.
class User(AbstractBaseUser, PermissionsMixin):
name = models.CharField(max_length=40, null=True, blank=True)
surname = models.CharField(max_length=40, null=True, blank=True)
objects = UserManager()
A serializer for the custom model:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'subject', 'user')
And a viewset with overwritten create method:
class GroupViewSet(viewsets.ModelViewSet):
serializer_class = GroupSerializer
def create(self, request, *args, **kwargs):
group = group.objects.create(user=self.request.user)
serializer = GroupSerializer(group)
return Response(serializer.data)
When calling a POST a new Group is created. The Group has relation to the User, but other fields (name, subject) are empty.
On the other hand, when doing the request serialization, the User on the object is empty.
def create(self, request, *args, **kwargs):
serializer = GroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How do I connect those 2 to make it work?
That makes sense since you never used the serializer to deserialize the request, and thus read the details passed in the POST request. You can work with:
def create(self, request, *args, **kwargs):
serializer = GroupSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=self.request.user)
else:
# return response when the serializer rejects the input
pass
# return a response
pass
In the serializer you mark the user field as read_only, to prevent the serializer from failing in case the user was not part of the request:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ('id', 'name', 'subject', 'user')
read_only_fields = ('user', )
I'm working on a Rest API for a web blog, I have made the custom user class and related between custom user class and the post class, but when i try to make a post request and add a post to the database i'm stuck in serializering the post because of the the relation between the user and the post models, all i want to do is just make the author of the post is the current logged in user but i don't actually know how to do this
Custom user model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True, blank=False, null=False)
username = models.CharField(max_length=255, unique=True, blank=False, null=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = CustomUserManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_superuser
Post model:
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, null=False)
content = models.CharField(max_length=10000, blank=False, null=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
Post serializer:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
# I don't know if this way is right
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data, request):
post = Post(
title=validated_data['title'],
content=validated_data['content'],
# I don't know what's gonna be assigned to the author
author=
)
post.save()
return post
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
At first, it doesn't seem to me like you need to specify your custom create method in PostSerializer. And author field can't be read_only otherwise it will be ignored and won't be saved. So it could just look like this:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
Then this is how you assign an author in your view action:
def post(self, request):
current_user = request.user
serializer = serializers.PostSerializer(data={**request.data, author=current_user})
...
I found a way that works fine for me.
Pass the context to the serializer from the view
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Then here is the serializer. Post Serializer
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data):
user = self.context['request'].user
post = Post(
title=validated_data['title'],
content=validated_data['content'],
author=user
)
post.save()
return post
I have a PATCH endpoint to change some non required data for a "Site". Through the endpoint you should be able to edit the description and supplier from a Site. The description is already working on the existing project. When I try to add the supplier to the PATCH, it doesn't update it..
View:
class AdminSiteDetailView(GenericAPIView):
def get_permissions(self):
return IsAuthenticated(),
def get_serializer_class(self):
return AdminSiteDetailSerializer
#swagger_auto_schema(
responses={
200: openapi.Response(
_("Successfully"),
AdminSiteDetailSerializer,
)
}
)
def get(self, request, site_pk):
"""
GET the data from the site.
"""
#somecode
#swagger_auto_schema(
responses={
200: openapi.Response(
_("Successfully"),
AdminSiteDetailSerializer,
)
}
)
def patch(self, request, site_pk):
"""
PATCH the description of the site.
"""
site = get_object_or_404(Site, pk=site_pk)
serializer_class = self.get_serializer_class()
serializer = serializer_class(site, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
site.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Serializer:
class AdminSiteSerializer(serializers.ModelSerializer):
supplier = serializers.SerializerMethodField()
class Meta:
model = Site
fields = [
"id",
"name",
"supplier",
]
#staticmethod
def get_supplier(site):
if not site.supplier:
return None
return SupplierSerializer(site.supplier).data
class AdminSiteDetailSerializer(AdminSiteSerializer):
class Meta(AdminSiteSerializer.Meta):
fields = AdminSiteSerializer.Meta.fields + ["description"]
class SupplierSerializer(serializers.ModelSerializer):
class Meta:
model = Supplier
fields = ("id", "name")
model:
class Site(models.Model):
class Meta:
ordering = ("name",)
name = models.CharField(max_length=250)
description = models.TextField(blank=True, null=True)
supplier = models.ForeignKey(
Supplier, on_delete=SET_NULL, blank=True, null=True, related_name="sites"
)
SerializerMethodField is read-only field. So you cannot use it to update data. Instead of using it in your case you can override serializer's to_representation method:
class AdminSiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = [
"id",
"name",
"supplier",
]
def to_representation(self, instance):
data = super().to_representation(instance)
if not instance.supplier:
data['supplier'] = None
data['supplier'] = SupplierSerializer(site.supplier).data
return data
I was using a standard Django user model but then requirements changed and I had to resort to an abstract user model. I updated my serializer to reflect the changes in model fields as well as form fields but I'm now getting an error when attempting to creating a user object. The error message is KeyError: 'unl_email' but also shows up for all the other fields. The validated_data for the create() function in serializers.py appears to be empty when I print. What am I doing wrong?
Views.py
class SignUp(CreateAPIView):
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = SignUpSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response({"success": "Registration succesful"}, status=status.HTTP_201_CREATED)
def perform_create(self, serializer):
serializer.save()
Models.py
class User(AbstractUser):
unl_num = models.AutoField(primary_key=True)
unl_usu_num = models.IntegerField(blank=True,null =True)
unl_date_added = models.DateField(default=timezone.now)
unl_name = models.CharField(max_length=256)
unl_surname = models.CharField(max_length=256)
unl_email = models.EmailField(max_length=200, unique=True)
unl_mobile = models.CharField(max_length=256)
unl_identity= models.CharField(max_length=256)
unl_oss_num= models.CharField(max_length=256)
unl_race= models.CharField(max_length=32)
unl_gender= models.CharField(max_length=32)
unl_disability= models.CharField(max_length=32)
unl_location= models.CharField(max_length=256)
unl_province= models.CharField(max_length=256)
unl_avatar= models.CharField(max_length=256)
USERNAME_FIELD = 'unl_email'
Serializers.py
class SignUpSerializer(serializers.Serializer):
class Meta:
model = User
fields = ('unl_email','password','unl_name',
'unl_surname','unl_mobile','unl_identity',
'unl_oss_num','unl_race','unl_gender','unl_disability',
'unl_location','unl_province','unl_avatar'
)
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create(
unl_email=validated_data['unl_email'],
unl_name=validated_data['unl_name'],
unl_surname=validated_data['unl_surname'],
unl_mobile=validated_data['unl_mobile'],
unl_identity=validated_data['unl_identity'],
unl_oss_num=validated_data['unl_oss_num'],
unl_race=validated_data['unl_race'],
unl_gender=validated_data['unl_gender'],
unl_disability=validated_data['unl_disability'],
unl_location=validated_data['unl_location'],
unl_province=validated_data['unl_province'],
unl_avatar=validated_data['unl_avatar']
)
user.set_password(validated_data['password'])
user.save()
return user
You're using serializers.Serializer and then specifying a model attribute for that serializer, which it does not use. Change it to serializers.ModelSerializer instead.
I can't get a valid response, always get this error
{"required": "This field is required.", "null": "This field may not be null.", "not_a_list": "Expected a list of items but got type \"{input_type}\".", "empty": "This list may not be empty."}
Here is some code (models, serializers, views)
models
class Task(models.Model):
name = models.CharField(max_length=50)
description = models.CharField(max_length=500, blank=True, default='')
pub_datetime = models.DateTimeField()
priority = models.CharField(choices=PRIORITY_CHOICES, max_length=1)
status = models.BooleanField(default=False)
owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, blank=True, default=None, null=True)
def __str__(self):
return self.name
serializers
class TaskSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
agenda = serializers.ReadOnlyField(source='agenda.name')
class Meta:
model = Task
fields = ('id', 'name', 'description', 'pub_datetime', 'priority', 'status', 'owner', 'agenda')
views
class TaskListView(generics.ListCreateAPIView):
serializer_class = TaskSerializer
permission_classes = (permissions.IsAuthenticated,)
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'task_list.html'
def get(self, request, *args, **kwargs):
queryset = Task.objects.filter(owner=self.request.user)
if self.request.accepted_renderer.format == 'html':
return Response({'tasks': queryset})
else:
serializer = TaskSerializer(data=queryset, many=True)
if serializer.is_valid():
return JsonResponse(serializer.data, safe=False)
else:
return JsonResponse(serializer.error_messages)
def post(self, request, *args, **kwargs):
name = request.data['name']
description = request.data['description']
priority = request.data['priority']
new_task = Task.objects.create(name=name, description=description, pub_datetime=datetime.datetime.now(),
priority=priority, status=False, owner=self.request.user)
new_task.save()
return redirect('task-list')
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
I'm not sure, at what place I'm wrong. Can someone help?
just text to avoid errors
The queryset or object instance should be passed to serializer as instance, not data, so:
queryset = Task.objects.filter(owner=self.request.user)
serializer = TaskSerializer(instance=queryset, many=True)
Also, I believe that serializer can't validate instance, so in this case you should be able to just return return JsonResponse(serializer.data)