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
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 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 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)
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.
blog/models.py
class Tag(models.Model):
label = models.SlugField(null=False, blank=False, unique=True)
# (iii) or here?
# ...
class Entry(models.Model):
title = models.CharField(null=False, blank=False, max_length=128)
content = models.TextField(null=False, blank=False)
slug = models.SlugField(unique=True)
tags = models.ManyToManyField(Tag, null=False, blank=False)
dtime_created = models.DateTimeField(null=False, blank=False, auto_now_add=True)
api/serializers.py
from blog.models import *
from rest_framework import serializers
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = ("title", "content")
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ("label")
# (ii) here?
api/views.py
class TagList(APIView):
def search_by_query(self, query):
try:
return Tag.objects.filter(label__icontains=re.escape(query))
# (i) do something here?
except Tag.DoesNotExist:
raise Http404
def get(self, request, format=None):
q = None
try:
q = request.POST["q"]
except KeyError:
return Response(status=status.HTTP_400_BAD_REQUEST)
tags = self.search_by_query(query)
serializer = TagSerializer(tags, many=True)
return Response(serializer.data)
def post(self, request, format=None):
if not request.user.is_authenticated():
return Response(status=status.HTTP_403_FORBIDDEN)
serializer = TagSerializer(request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
My problem is a little bit complicated. Now, I have a Tag model which is used by Entry model. Then, I created an APIView called TagList which dumps string containing tags. However, the problem is, I also want to dump how many times a tag is used in Entry instances, like below:
# This is similiar to how it dumps
[
{
"label": "a-label-here"
},
{
"label": "another-label-here"
}
]
# This is what I want
[
{
"label": "a-label-here",
"length": 5
},
{
"label": "another-label-here",
"length": 15
}
]
How to do it?
Environment
python 3.5.1
django 1.9.5
djangorestframework 3.3.3
It's pretty simple - use the source core argument of DRF.
Step 1
Define on your Tag model a method that computes the number of occurrences of any tag:
class Tag(models.Model):
label = models.SlugField(null=False, blank=False, unique=True)
def nr_of_entries(self):
return self.entry_set.count()
Step 2
Then on your TagSerializer add a custom field that will take its data from the method you defined above:
class TagSerializer(serializers.ModelSerializer):
length = serializers.IntegerField(source="nr_of_entries", read_only=True)
class Meta:
model = Tag
fields = ("label", "length")
Now, when an instance of a tag will be serialized, it will contain a length property that will take its value from the result of running the nr_of_entries method on that tag instance.