I have a view in Django that fetches some objects, adds new attribute to them and returns them as JSON response.
The code looks like this:
def stats(request):
items = MyItem.objects.all().order_by('-id')
for item in items:
item.new_attribute = 10
items_json = serializers.serialize('json', items)
return HttpResponse(items_json, content_type='application/json')
The new_attribute is not visible in JSON response. How can I make it visible in JSON response? It can be accessed in templates normally with {{ item.new_attribute }}.
EDIT: I'm using default Django serializer (from django.core import serializers)
Default serializer looks into fields defined in model. Thus, there are two options to resolve it:
Add the attribute to your model MyItem
Use a custom serializer to serialize your model with this dynamic attribute
Example of such serializer:
serializers.py
class MyItemSerializer(serializers.ModelSerializer):
new_attribute = serializers.SerializerMethodField()
def get_new_attribute(self, obj):
if hasattr(obj, 'new_attribute'):
return obj.new_attribute
return None
class Meta:
model = MyItem
fields = '__all__'
And then in views.py it will be:
items_json = serializers.MyItemSerializer(items, many=True).data
Since you are reference serializers in your code, I assume that you are using Django REST Framework and have a class named MyItemSerializer. This class tells is used by serializers.serialize() to determine how to create the JSON string from your model objects. In order to add a new attribute to the JSON string, you need to add the attribute to this class.
Related
I have a model that looks like this:
class Foo(models.Model):
unique_field = models.URLField(unique=True)
another_field = models.TextField()
# etc...
and its corresponding ViewSet looks like this (bound to /foo/):
class FooViewSet(
viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.CreateModelMixin,
mixins.DestroyModelMixin,
):
queryset = Foo.objects.all()
serializer_class = FooSerializer
When I make a POST request to /foo/, if there's no clash I just want the default behaviour (create a new row in the database and return its serialized form as a json blob).
The problem is that when there is a clash the server just throws an error. What I'd rather have it do is return the json blob for the existing data, rather than crashing. Or at the very least return some kind of descriptive error.
Is there a way to get this behaviour without manually overriding create()?
So I have this serializer that adds an attribute. Let's say I want to serialize Post data, which is like a post on Facebook or Twitter. When I serialize it lets say I want to pass a variable to the serializer region, because I use that variable to calculate an attribute in the Serializer that I want. So for example I'd like to do something like PostSerializer(post_object, region='Italy') and it'll use region='Italy' for calculating an attribute which is a serializers.SerializerMethodField. How can I pass a variable to my serializer that is used to calculae a serializers.SerializerMethodField()?
serializer.py
class PostSerializer(serializers.ModelSerializer):
post_count_from_region = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('author', 'body', 'created', 'updated_at', 'post_count_from_region')
def get_post_count_from_region(self, post_obj, region:str):
return Post.objects.filter(region=region).count()
So for a more explicit example of what this use case looks like is:
view.py
def get_full_post_data_by_uuid(request, post_uuid, region: str):
post_obj = Post.objects.get(pk=post_uuid)
return Response(PostSerializer(post_obj, region=region).data, status=200)
So this is what context is for in Serializers on Django. context takes in a dictionary so you can do.
PostSerializer(post_object, context={'region':'Italy'})
then under serializer
def get_post_count_from_region(self, post_obj):
region = self.context.get('region', <default value>)
return Post.objects.filter(region=region).count()
With some help, I solved this issue.
My api is work, but today I found this error when I try to access '/api/v1/docs'
AttributeError at /api/v1/docs/
'NoneType' object has no attribute 'method'
I know that the error is here:
def get_fields(self):
fields = super().get_fields()
if self.context['request'].method in ['POST', 'PATCH', 'PUT']:
fields['products'] = serializers.ListField(
write_only=True,
child=serializers.IntegerField()
)
return fields
When I remove .method, the access to the /api/v1/docs/ works, but my solution to post some products in bundleproducts, doesn't work.
My code:
view.py
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
model = Product
class BundleProductViewSet(viewsets.ModelViewSet):
queryset = BundleProduct.objects.all()
serializer_class = BundleProductSerializer
model = BundleProduct
class BundleViewSet(viewsets.ModelViewSet):
queryset = Bundle.objects.all()
serializer_class = BundleSerializer
model = Bundle
This is probably caused by this serializer being used as a nested serializer in another serializer. So lets say the definition for the serializer in question is:
class MySerializer(serializers.Serializer):
...
And you have another serializer like this:
class OtherSerializer(serializers.Serializer):
my_field = MySerializer()
In this case, when instantiating an instance of OtherSerializer, its context is not passed automatically to MySerializer, so there would not be a request in the context of MySerializer. You can either add the context to nested serializer manually, or in the get_fields method, check that request exists in self.context and proceed accordingly.
Also, I am not sure what you are trying to accomplish, but if you provide a field with
write_only=True
in serializer class definition, the field would not be present when reading the serializer, i.e for get requests in general, which seems like what you are trying to do here. So adding the products field as write_only would have the same effect, you do not need to override get_fields method
I want to access the request object in my Views.py and Serializers.py in DRF.
My Views.py:
class ProductViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
queryset = Product.objects.all()
serializer_class = ProductSerializer(context={'request': request})
My Serializers.py:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.request.user ??
class Meta:
model = Product
fields = (
'title', 'slug', 'product_stores', 'get_sr_price')
In Serializers.py I get ProductSerializer' object has no attribute 'request'. Also In views.py I get NameError: name 'request' is not defined
How do I access request object? Do I have to pass it from views to serializers? Also what's the difference between views.py and serializers.py? Generally I write all the business logic in Views.py ; here also should I do all the queries/filters in the views or should I do them in serializers or it doesn't make a difference. New to DRF please help.
You don't need to include request object in the context as the generic views passes request object to the serializer context.
DRF Source code snippet:
# rest_framework/generics.py
def get_serializer_context(self):
"""
Extra context provided to the serializer class.
"""
return {
'request': self.request, # request object is passed here
'format': self.format_kwarg,
'view': self
}
In your serializer, you can access the request object using .context attribute.
The context dictionary can be used within any serializer field logic,
such as a custom .to_representation() method, by accessing the
self.context attribute.
class ProductSerializer(serializers.HyperlinkedModelSerializer):
get_sr_price = serializers.SerializerMethodField('get_sr_price_func')
def get_sr_price_func(self, obj):
return self.context['request'].user # access the request object
Serializers are the way external data is mapped from / to models (Django or simple Python classes).
Views are dealing with how the data will be shown. Throttling, pagination, authentication are managed by the view. They also handle the data set.
DRF provides a context to pass request specific data to the serializer without having to redefine the init. This is likely what you're looking for.
I wrote an __init__ method for one of my models that adds some auxiliary information to the object by dynamically adding an attribute to the object that does not reflect a column in the database:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def __init__(self, *args, **kwargs):
self.aux_info = "I'm not in the database!"
This seemed to be working fine, but I found a case where it does not work. I have some code in a view where I set a status variable and package up a list of MyModels into json like so:
from django.core import serializers
from django.utils import simplejson
...
# have to use serializers for django models
serialized_items = serializers.serialize("json", itemlist)
data["items"] = serialized_items # serialized_items is now a string
data["status"] = status
# package up data dict using simplejson for python objects
resp = simplejson.dumps(data)
return HttpResponse(resp, mimetype="application/javascript")
The problem seems to be that django's serializers only serialize the model fields and not all attributes of the object so aux_info does not come through. I'm also pretty sure that using both serializers and simplejson is not the right way to do this. Thanks for any help!
Try usung the serialiser's optional fields argument.
serialized_items = serializers.serialize("json", itemlist, fields=['.....', 'aux_info'])
May i also suggest that using the __init__ method to add fields is considdered bad form in django and would be much better achieved like so:
class MyModel(models.Model):
title = Models.CharField()
description = Models.TextField()
def aux_info(self):
return "I'm not in the database!"