I have messages and attachments to them (photos, documents, etc.). When I pass data to the serializer, I find that it is empty.
serializer:
class MessageAttachmentSerializer(serializers.ModelSerializer):
file = serializers.FileField()
class Meta:
model = MessageAttachment
fields = ("file", "message", "size")
view:
#api_view(["POST"])
def stuff_message(request):
data = request.data.copy()
data["sender"] = request.user.id
serializer = MessageSerializer(data=data, context={"request": request})
serializer.is_valid(True)
saved_msg = serializer.save()
if request.FILES:
data["message"] = saved_msg
attachment_serializer = MessageAttachmentSerializer(data=data, context={"request": request}, many=True)
attachment_serializer.is_valid(True)
attachment_serializer.save()
try:
bot.send_message(serializer.validated_data["receiver"].telegram_id, text=serializer.validated_data["text"])
except ApiTelegramException:
return Response("Chat undefined.", status=status.HTTP_404_NOT_FOUND)
return Response()
My message serializer works fine. When trying to output validated_data or data from MessageAttachment I get an empty list. Doesn't throw errors.
Corrected to:
#api_view(["POST"])
def stuff_message(request):
request.data._mutable = True
request.data["sender"] = request.user.id
serializer = MessageSerializer(data=request.data)
serializer.is_valid(True)
saved_msg = serializer.save()
request.data["message"] = saved_msg.id
attachments = []
for attachment in request.FILES.getlist("file"):
record = request.data
record["file"] = attachment
attachment_serializer = MessageAttachmentSerializer(data=record)
attachment_serializer.is_valid(True)
saved_attachment = attachment_serializer.save()
attachments.append(saved_attachment.file)
try:
bot.send_message(serializer.validated_data["receiver"].telegram_id, text=serializer.validated_data["text"])
for attachment in attachments:
bot.send_document(serializer.validated_data["receiver"].telegram_id, attachment.file)
except ApiTelegramException:
return Response("Chat undefined.", status=status.HTTP_404_NOT_FOUND)
return Response()
Just a guess but make sure you are returning the .data attribute on your serialized data as in:
output = MessageAttachmentSerializer(data=data).data
Reference: https://www.django-rest-framework.org/api-guide/serializers/
It was the "many" argument. It looks like cases with one file and several need to be handled differently when creating data through a serializer.
Related
Here is the simplified verison of the Serializer what I have:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
def __init__(self, *args, **kwargs):
init = super().__init__(*args, **kwargs)
return init
username = serializers.ReadOnlyField(source='user.username')
user_id = serializers.ReadOnlyField(source='user.id')
user = UserSerializer(read_only=True)
url_field_name = 'url_api'
# and more definitions
content_type_id = serializers.IntegerField()
site_id = serializers.SerializerMethodField('_siteId')
def _siteId(self, threadedcomment):
site_id = settings.SITE_ID
return site_id
class Meta:
model = ThreadedComment
fields = ('url_api','url','id','title','tree_path','comment','submit_date','submit_date_unix','submit_date_humanized','root_id','is_removed',
'parent_id','last_child_id','newest_activity','depth','username','user_id','object_pk','content_type_id','user',
'site_id',
)
read_only_fields = ('id','title','tree_path','submit_date','root_id','is_removed',
'parent_id','last_child_id','newest_activity','depth','username','user_id',
# 'site_id',
)
class CommentViewSet(viewsets.ModelViewSet):
queryset = ThreadedComment.objects.all().annotate().all()
serializer_class = CommentSerializer
permission_classes = []
filter_backends = [filters.OrderingFilter]
def filter_queryset(self, queryset):
return queryset
def list(self, request):
return super().list(request)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# site_id = settings.SITE_ID
# serializer.save(user=self.request.user, site_id=site_id, )
return super().perform_create(serializer)
Now I make an http post request on the api as i.e.:
axios.post('/api/comments/', {
"comment":"test.",
"object_pk":34,
"content_type_id":12,
},
It shows me an error:
500 (Internal Server Error)
IntegrityError: NOT NULL constraint failed: django_comments.site_id
The problem (a pure question) is, why the SerializerMethodField doesn't work? I put a breakpoint on the site_id = settings.SITE_ID line, but it doesn't hit, which means the line hasn't even executed.
I also tried putting a set of lines (the commented lines) on perform_create, reading a SO post, but the result is the same, the same error, NOT NULL constraint failed.
I'm certainly passing a value, but it shows it's a null value, saying nothing is passed, what this means? What am I donig wrong here? Thanks.
I have an endpoint, X, which spits out json like a charm. The same resource can be generated into a binary variant. X's endpoint is made by a viewset, and the binary version of X has its own endpoint with the help of the action-decorator.
class XViewSet(ReadOnlyModelViewSet):
queryset = X.objects.all()
serializer_class = XSerializer
#action(detail=True, methods=['get'])
def binary(self, request, pk=None):
x = self.get_object()
binx = x.get_binary(FORMAT)
..
Obviously, binary will never spit out json. How do I get a hold of the negotiated FORMAT, and how do I tell django-rest-framework about the binary formats supported by binary?
You shouldn't return the binary data from the ViewSet but have a custom renderer converting it:
from rest_framework.renderers import BaseRenderer, JSONRenderer
class BinaryRenderer(BaseRenderer):
media_type = 'application/octet-stream'
format = 'bin'
render_style = 'binary'
charset = None
def render(self, data, media_type=None, renderer_context=None):
# Either use `data` or access the view via
# the `renderer_context`
view = renderer_context['view']
return view.get_object().get_binary()
class XViewSet(ReadOnlyModelViewSet):
queryset = X.objects.all()
serializer_class = XSerializer
renderer_classes = (JSONRenderer, BinaryRenderer)
Check out the documentation on how the renderer is determined.
I have an application in Django 2.0 in which I use a template with an ajax function from which I want to receive the result of a filter but it generates the following error:
TypeError: <QuerySet [<Curso: Curso object (1)>, <Curso: Curso object (2)>, <Curso: Curso object (3)>]> is not JSON serializable
Views.py
def activaAjax(request):
curso = Curso.objects.filter(pk = request.GET['id'])
cursos = Curso.objects.all()
try:
curso.update(estado=Case(When(estado=True, then=Value(False)),When(estado=False, then=Value(True))))
mensaje = "Proceso de ACTIVACIÓN/INACTIVACIÓN correcto!!!"
data = {'mensaje': mensaje, 'cursos':cursos}
return HttpResponse(json.dumps(data), content_type="application/json")
except:
return HttpResponse(json.dumps({"mensaje":"Error"}), content_type='application/json', status = 500)
return HttpResponse(json.dumps({"mensaje":"Error"}), content_type='application/json')
Queryset can not be directly dump in json by json.dumps()
either you should write queryset.values_list('field1',flat=True)
or if you want more than 1 field from object you should write queryset.values_list('field1','field2',..)
convet it to list with list(queryset.values_list('field1','field2',..))
and pass it in your data as
data = { 'corsos' : list(queryset.values_list('field1','field2',..)) }
2) Or you can also do
from django.core import serializers
serialized_qs = serializers.serialize('json', queryset)
data = {"queryset" : serialized_qs}
return JsonResponse(data)
To make it short:
I have a serializer (django rest framework) that has two custom fields which do not directly correspond to a field of my model and also have a different name. The to_internal_value() method (probably) works, but I don't know how to access the post data of these fields.
And in case you need more details on my case:
I have a django model that looks like this:
class Requirement(models.Model):
job = models.ForeignKey('Job', related_name = 'requirements')
description = models.CharField(max_length = 140)
is_must_have = models.BooleanField() # otherwise is of type b
class Job(models.Model):
...
I want to serialize it in a manner that a job object will look like this:
{ "must_have": [must have requirements], "nice:to_have": [nice to have requirements] }
Therefore, I have custom fields in my serializer for jobs:
class JobSerializer(serializers.Serializer):
nice_to_have = NiceToHaveField(source = 'requirements', allow_null = True)
must_have = MustHaveField(source = 'requirements', allow_null = True)
The NiceToHaveField and the MustHaveField classes simpy override the to_representation() and the to_internal_value() methods, the to_representation also sorts the requirements after type.
But the validated_data in JobSerializer.create never contain these cutom fields. I know the to_internal_value gets called and does its work, but the results are not accessable.
What is the way to solve this?
I found a solution I don't like, there is probably a better way to do this. Anyways, the data is available in the view.request.data. So I used the perform_create hook like this:
def perform_create(self, serializer):
nice_to_have = None
must_have = None
if 'nice_to_have' in self.request.data and self.request.data['nice_to_have'] != None:
field = NiceToHaveField()
nice_to_have = field.to_internal_value(self.request.data['nice_to_have'])
if 'must_have' in self.request.data and self.request.data['must_have'] != None:
field = MustHaveField()
must_have = field.to_internal_value(self.request.data['must_have'])
serializer.save(owner = self.request.user, nice_to_have = nice_to_have, must_have = must_have)
I'm following this link (http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers) to write nested serializer. But when I pop the 'vars' from validated_data in the create method of HostSerializer, I found it's empty.
I'm using django 1.9.2 and django restframework 3.3.2.
My model:
class Host(models.Model):
name = CharField(max_length=20, primary_key=True)
vm_cpu = IntegerField(default=2)
vm_mem = IntegerField(default=2048)
create_vm = BooleanField(default=True)
def __unicode__(self):
return('%s' % (self.name))
class Variable(models.Model):
name = CharField(max_length=10)
value = CharField(max_length=20)
host = models.ForeignKey(Host, related_name='vars')
def __unicode__(self):
return('%s=%s' % (self.name, self.value))
Serializer
class VariableSerializer(ModelSerializer):
class Meta:
model = Variable
class HostSerializer(ModelSerializer):
vars = VariableSerializer(many=True)
class Meta:
model = Host
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
host = Host.objects.create(**validated_data)
for v in vars_data:
Variable.objects.create(host = host, **v)
return host
This is the problem I found vars_data is an empty list:
def create(self, validated_data):
# i set a break point here and found vars_data is empty
vars_data = validated_data.pop('vars')
Here's the rest of the code
admin.py
class VariableAdmin(admin.ModelAdmin):
list_display = ['name', 'value']
class HostAdmin(admin.ModelAdmin):
list_display = ['name']
admin.site.register(Variable, VariableAdmin)
admin.site.register(Host, HostAdmin)
urls.py
router = DefaultRouter()
router.register(r'variables', VariableViewSet, base_name='variables')
router.register(r'hosts', HostViewSet, base_name='hosts')
urlpatterns = [
url(r'^', include(router.urls)),
]
views.py
class VariableViewSet(ModelViewSet):
queryset = Variable.objects.all()
serializer_class = VariableSerializer
class HostViewSet(ModelViewSet):
queryset = Host.objects.all()
serializer_class = HostSerializer
My test program
post.py
import json
import requests
file = 'host.json'
url = 'http://localhost:8001/test_nest/hosts/'
with open(file, 'r') as f:
j = f.read()
data = json.loads(j)
r = requests.post(url, data = data)
print r.text
And here's the test data
host.json
{
"name": "host4",
"vars": [
{
"name": "var2-a",
"value": "a1"
},
{
"name": "var2-b",
"value": "a2"
}
],
"vm_cpu": 2,
"vm_mem": 2048,
"create_vm": true
}
I'm new to django. So I'm wondering if it's something simple and obvious. Did I use the wrong viewset? Did I post to the wrong URL? Or I setup the URL structure wrong?
I your serializers try using...
def update(self, instance,validated_data):
instance.vars_data = validated_data.get('vars',instance.vars)
instance.host = Host.objects.create(**validated_data)
for v in vars_data:
v,created=Variable.objects.create(host = host, **v)
instance.v.add(v)
return host
The following code works for me. Maybe you can try it:
models.py
class UserProfile(AbstractUser):
pass
class TobaccoCert(models.Model):
license = models.CharField(primary_key=True, max_length=15)
license_image = models.ImageField(upload_to="certs", blank=True, null=True, verbose_name='cert image')
views.py
class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
serializer_class = UserRegSerializer
...
def create(self, request, *args, **kwargs):
data = request.data
# here i make my nested data `certs` and update it to data
image_data = request.FILES.get('images')
_license = data.get('username', None)
certs = {'license': _license, 'license_image': image_data}
data['certs'] = certs
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer) # save it
ret_dict = serializer.data
payload = jwt_payload_handler(user)
ret_dict["token"] = jwt_encode_handler(payload) # 获取token
ret_dict["name"] = user.name if user.name else user.username
headers = self.get_success_headers(serializer.data)
return Response(ret_dict, status=status.HTTP_201_CREATED, headers=headers)
serializer.py
class CertSerializer(serializers.ModelSerializer):
pass
class UserRegSerializer(serializers.ModelSerializer):
# [Serializer relations - Django REST framework](https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers)
# this line is vars of your code
certs = CertSerializer(many=True,write_only=True)
...
password = serializers.CharField(
style={'input_type': 'password'}, help_text="passowrd", label="passowrd", write_only=True,
)
class Meta:
model = User
fields = ("username", "mobile", "password", 'province', 'city', 'dist', 'code', 'certs')
def create(self, validated_data):
"""
"""
# here we get certs
certs_data = self.initial_data.get('certs')
# just pop certs because it is useless for user create
certs_empty = validated_data.pop('certs')
user = super().create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
# use certs data to create cert
image_url = self.save_image(certs_data)
_license = validated_data.get('username', None)
TobaccoCert.objects.create(license=_license, license_image=image_url)
return user
In short, you need to add your nested parameters directly in the request parameters. In my example code, it is certs which is vars in your example and then use certs_data = self.initial_data.get('certs') get the parameters you passed in create method.
Another possible way is post your data before your requests:
In postman:
enter image description here
in request:
python - Django Rest Framework writable nested serializer with multiple nested objects - Stack Overflow
In addition, you can try to modify queryDict directly. You can refer to this link and here
Some useful links
django - Need help understanding many and source fields in a serializer - Stack Overflow
python - Django rest framework writeable nested serializer data missing from validated_data - Stack Overflow