I want to realize POST method to create Article object.
I have to provide this body:
{
"title": "Sample title",
"link": "https://www.sample.com/"
}
And get Article object with auto assigned ID, author_name(current user) and creation_date.
But i got an problem - "create" methor deletes one field - author_name. (See code below)
Finally i get next error msg:
IntegrityError at /api/articles/
Error: Null value in column "author_name_id" with NOT NULL
DETAIL: Error row contains (32, Sample title, https://www.sample.com/, 2020-11-08, null).
How do i solve my problem?
models.py
class Article(models.Model):
title = models.CharField(
name='title',
max_length=120,
null=False,
)
link = models.URLField(
name='link',
unique=True,
null=False,
)
creation_date = models.DateField(
name='creation_date',
default=date.today,
null=False,
)
author_name = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='articles',
null=False,
)
def __str__(self):
return self.title
views.py
class ArticleView(APIView):
def post(request):
if request.user.is_authenticated:
data = {"author_name": request.user}
data.update(request.data)
print(request.data)
serializer = ArticleSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(status=201)
return Response(status=403)
request.data >>> {'title': 'Sample title', 'link': 'https://www.sample.com/', 'author_name': <SimpleLazyObject: <User: admin>>}
serializers.py
class ArticleSerializer(serializers.ModelSerializer):
author_name = serializers.StringRelatedField()
class Meta:
model = Article
fields = "__all__"
def create(self, validated_data):
print(validated_data)
return Article.objects.create(**validated_data)
validated_data >>> {'title': 'Sample title', 'link': 'https://www.sample.com/'}
I think it is because of the author_name = serializers.StringRelatedField() you have on your serializers. By default this is a read-only field so the serializer will remove it from the data you pass to it.
I'm pretty sure if you would just do:
serializer = ArticleSerializer(request.data)
if serializer.is_valid():
serializer.save(author_name=request.user)
this should solve your problem. Because on the serializer you validate the fields that are not read-only and then you save the data by passing the user instance to the save method of the serializer.
I also see you are using an ApiView for creating a model instance, I would recommend looking into CreateApiView
Related
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
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.
I'm getting KeyError: 'request' while i want to get the current user id through user request.
I tried something like this: validated_data['user_id'] = CarOwnerCarDetails.objects.get(user_id=self.context['request'].user.id) but it's throwing me KeyError.
How to get the current user id through request in serializers?
if any help would be much appreciated. Thank you so much in advance my friends.
models :
class CarOwnerCarDetails(models.Model):
user_id = models.OneToOneField(User, on_delete=models.CASCADE)
car_plate_number = models.CharField(max_length=20, null=True, blank=True)
class GetQuotes(models.Model):
user = models.ForeignKey(CarOwnerCarDetails, on_delete=models.CASCADE, blank=True, null=True)
subject = models.CharField(max_length=240, blank=False, null=True)
serializers :
class ShopGarageGetQuoteSerializer(ModelSerializer):
subject = CharField(error_messages={'required':'subject key is required', 'blank':'subject is required'})
user_id = serializers.CharField(read_only=True)
class Meta:
model = GetQuotes
fields= ['user_id', 'subject']
def create(self,validated_data):
subject = validated_data['subject']
validated_data['user_id'] = CarOwnerCarDetails.objects.get(user_id=self.context['request'].user.id)
quotes_obj = GetQuotes.objects.create(
subject=subject,
user_id=validated_data['user_id']
)
return validated_data
views.py :
class ShopGarageGetQuoteAPIView(APIView):
permission_classes = (IsAuthenticated,)
def post(self,request,*args,**kwargs):
data = request.data
serializer = ShopGarageGetQuoteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'success' :'True','message' : 'Quotes send successfully','data' : serializer.data},status=200)
return Response(serializer.errors,status=400)
as is written in the Official Documentation the user information (if the Authentication framework is correctly setup) is available using request.user
In your View you have to pass it using the serializer's context
class ShopGarageGetQuoteAPIView(APIView):
permission_classes = (IsAuthenticated,)
def post(self,request,*args,**kwargs):
data = request.data
context = {'request': request}
serializer = ShopGarageGetQuoteSerializer(data=request.data, context=context)
if serializer.is_valid():
serializer.save()
return Response({'success' :'True','message' : 'Quotes send successfully','data' : serializer.data},status=200)
return Response(serializer.errors,status=400)
For Generic Views/Viewsets the standard context contains 'request', 'view' and 'format' but in a standard APIView you have to pass it manually
I am a beginner to django and trying to create a post request on django rest-framework.
I have a following model:
class ProjectScheme(models.Model):
name = models.CharField(max_length=250, blank=False,null=False)
parent_scheme_id = models.ForeignKey(ProjectSchemeMaster, on_delete = models.CASCADE)
rule = models.TextField(blank=True)
def __str__(self):
return str(self.name)
And a serializer:
class SchemeDetailSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectScheme
fields = ('id', 'name', 'parent_scheme_id', 'rule')
depth=1
And my view:
#api_view(['POST'])
#renderer_classes((JSONRenderer,))
def create_project_scheme(request):
if request.method == 'POST':
data = JSONParser().parse(request)
serializer = SchemeDetailSerializer(data=data)
comp_logger.info('Call to create_project')
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response({'response':serializer.errors})
return Response({})
With post request body as:
{
"name": "django-rf"
}
This gives serializer.is_valid() to true, but in response I get
(1048, "Column 'parent_scheme_id_id' cannot be null")
I tried adding parent_scheme_id = models.ForeignKey(ProjectSchemeMaster, on_delete = models.CASCADE, blank=False, null=False) but that didn't make any difference.
How can I validate the request input so that it shows proper validation message like for name field?
In your model, you set your ForeignKey field as a required field, Django by default consider a field required=True if not explicitely provide null=True. So if you want to create ProjectScheme instance without a ForeignKey referrence, then make sure you provide null=True to your ForeignKey field,
class ProjectScheme(models.Model):
name = models.CharField(max_length=250, blank=False,null=False)
parent_scheme_id = models.ForeignKey(ProjectSchemeMaster, null=True, blank=True, on_delete = models.CASCADE)
rule = models.TextField(blank=True)
def __str__(self):
return str(self.name)
in mention, blank=True works in form level validation.
I know on this topic people asked a question before but my case is different and I have implemented almost all the solutions which I found but none of them are worked for me.
First I have defined three classes in models:
models.py
class User(AbstractBaseUser, PermissionsMixin):
""" User Model """
username = None
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
agency = models.ForeignKey('agency.Agency', on_delete=models.CASCADE, null=True)
weekly_email = models.NullBooleanField()
is_create_new_password = models.NullBooleanField(default=True)
is_active = models.BooleanField(default=True)
last_login_time = models.DateTimeField(null=True)
last_login_from = models.CharField(max_length=255, null=True)
created_at = models.DateField(default=timezone.now)
updated_at = models.DateField(default=timezone.now)
created_by = models.IntegerField(null=True)
updated_by = models.IntegerField(null=True)
""" The `USERNAME_FIELD` property tells us which field we will use to log in.
In this case, we want that to be the email field. """
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username"]
""" Tells Django that the UserManager class defined above should manage
objects of this type. """
objects = UserManager()
class Role(models.Model):
""" Role Model """
name = models.CharField(max_length=255, unique=True)
class UserRole(models.Model):
""" User Role Model """
user = models.ForeignKey(User, on_delete=models.CASCADE)
role = models.ForeignKey(Role, on_delete=models.CASCADE)
Then I have defined my serializer for user module:
serializers.py
class RegistrationSerializer(serializers.ModelSerializer):
""" Serializers registration requests and creates a new user. """
user_id = serializers.IntegerField(required=False)
email = serializers.EmailField(max_length=255)
name = serializers.CharField(max_length=255)
agency_id = serializers.IntegerField(source='agency.id', required=False)
role = serializers.CharField(source='role.name')
weekly_email = serializers.NullBooleanField()
last_login_time = serializers.DateTimeField(required=False)
last_login_from = serializers.CharField(max_length=255, required=False)
class Meta:
model = User
fields = (
'role', 'user_id', 'email', 'name', 'agency_id', 'weekly_email', 'last_login_time',
'last_login_from'
)
And At the end, I have defined my view file for user creation:
views.py
class UserCreateAPIView(APIView):
""" User create Api view class """
#Allow any user (authenticated or not) to hit this endpoint.
permission_classes = (IsAuthenticated,)
serializer_class = RegistrationSerializer
def post(self, request):
""" create user using following logic. """
request.data['user_id'] = request.user.id
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
return Response({'message': response['user']['created'], 'data': serializer.data},
status=status.HTTP_201_CREATED)
Now when I run it everything works fine like user is created, role is created as per my expectations. My view, serializer and models excuted but at the end on this line:
return Response({'message': response['user']['created'], 'data': serializer.data},
status=status.HTTP_201_CREATED)
I am facing error like,
AttributeError: Got AttributeError when attempting to get a value for field `role` on serializer `RegistrationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'role'.
I think you need to use ModelSerializer
class RegistrationSerializer(serializers.Serializer):
to
class RegistrationSerializer(serializers.ModelSerializer):